Bagaimana cara menguji apakah suatu variabel didefinisikan sama sekali di Bash sebelum versi 4.2 dengan opsi nounset shell?

16

Untuk versi Bash sebelum "GNU bash, Versi 4.2" apakah ada alternatif yang setara untuk -vopsi testperintah? Sebagai contoh:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Tim Friske
sumber
-vbukan pilihan untuk test, tetapi operator untuk ekspresi kondisional.
StackExchange for All
@Tim Ini adalah tiga hal, selain menjadi token, string dan bagian dari sebuah baris: An optionto a command test -v, an operatorto a conditional expressiondan a unary test primaryfor [ ]. Jangan mencampur bahasa Inggris dengan definisi shell.

Jawaban:

36

Portabel untuk semua kulit POSIX:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Buat itu ${foobar:+1}jika Anda ingin memperlakukan foobardengan cara yang sama apakah itu kosong atau tidak ditentukan. Anda juga dapat menggunakan ${foobar-}untuk mendapatkan string kosong saat foobartidak terdefinisi dan nilai foobarsebaliknya (atau masukkan nilai default lainnya setelah -).

Dalam ksh, jika foobardideklarasikan tetapi tidak didefinisikan, seperti dalam typeset -a foobar, kemudian ${foobar+1}memperluas ke string kosong.

Zsh tidak memiliki variabel yang dideklarasikan tetapi tidak disetel: typeset -a foobarmembuat array kosong.

Dalam bash, array berperilaku dengan cara yang berbeda dan mengejutkan. ${a+1}hanya mengembang ke 1jika aarray tidak kosong, mis

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

Prinsip yang sama berlaku untuk array asosiatif: variabel array diperlakukan sebagaimana didefinisikan jika mereka memiliki kumpulan indeks yang tidak kosong.

Cara berbeda, spesifik bash untuk menguji apakah variabel jenis apa pun telah ditetapkan adalah untuk memeriksa apakah variabel tersebut terdaftar . Laporan ini mengosongkan array seperti yang didefinisikan, tidak seperti , tetapi melaporkan variabel yang dideklarasikan tetapi tidak ditugaskan ( ) sebagai tidak terdefinisi.${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

Ini sama dengan menguji nilai pengembalian typeset -p foobarataudeclare -p foobar , kecuali yang typeset -p foobargagal pada variabel yang dideklarasikan tetapi tidak ditetapkan.

Di bash, seperti di ksh, set -o nounset; typeset -a foobar; echo $foobarmemicu kesalahan dalam upaya memperluas variabel yang tidak ditentukan foobar. Tidak seperti di ksh, set -o nounset; foobar=(); echo $foobar(atau echo "${foobar[@]}") juga memicu kesalahan.

Perhatikan bahwa dalam semua situasi yang dijelaskan di sini, ${foobar+1}perluas string kosong jika dan hanya jika $foobarakan menyebabkan kesalahan set -o nounset.

Gilles 'SO- berhenti menjadi jahat'
sumber
1
Bagaimana dengan array? Dalam versi Bash "GNU bash, versi 4.1.10 (4) -release (i686-pc-cygwin)" echo "${foobar:+1}"tidak mencetak 1jika declare -a foobarsebelumnya dikeluarkan dan dengan demikian foobarmerupakan array yang diindeks. declare -p foobarmelaporkan dengan benar declare -a foobar='()'. Apakah "${foobar:+1}"hanya berfungsi untuk variabel non-array?
Tim Friske
@TimFriske ${foobar+1}(tanpa :, saya membalikkan dua contoh dalam jawaban asli saya) benar untuk array dalam bash jika definisi Anda tentang "didefinisikan" adalah "akan $foobarbekerja di bawah set -o nounset". Jika definisi Anda berbeda, bash agak aneh. Lihat jawaban saya yang diperbarui.
Gilles 'SANGAT berhenti menjadi jahat'
1
Mengenai topik "Dalam bash, array berperilaku dengan cara yang berbeda dan mengejutkan." perilaku dapat dijelaskan dari bash (1) manual, bagian "Array". Ini menyatakan bahwa "Merujuk variabel array tanpa subskrip sama dengan referensi array dengan subskrip 0.". Jadi, jika tidak ada 0indeks atau kunci yang didefinisikan sebagaimana adanya a=(), ${a+1}benar tidak menghasilkan apa-apa.
Tim Friske
1
@TimFriske Saya tahu bahwa implementasi bash sesuai dengan dokumentasinya. Tetapi memperlakukan array kosong seperti variabel yang tidak terdefinisi adalah desain yang sangat aneh.
Gilles 'SANGAT berhenti menjadi jahat'
Saya lebih suka hasil dari: Bagaimana cara memeriksa apakah variabel diatur dalam Bash? -> The Right Way ... Saya membuat akord dengan cara bagaimana hal ini seharusnya bekerja. Berani saya katakan, Windows memiliki definedoperator. Test bisa melakukan itu; itu tidak sulit ( um ....)
akan
5

Singkatnya dengan jawaban Gilles saya membuat aturan berikut:

  1. Gunakan [[ -v foobar ]]untuk variabel dalam versi Bash> = 4.2.
  2. Gunakan declare -p foobar &>/dev/nulluntuk variabel array dalam versi Bash <4.2.
  3. Gunakan (( ${foo[0]+1} ))atau (( ${bar[foo]+1} ))untuk langganan masing-masing array yang diindeks ( -a) dan diketik ( -A) ( declare). Opsi 1 dan 2 tidak berfungsi di sini.
Tim Friske
sumber
3

Saya menggunakan teknik yang sama untuk semua variabel di bash, dan itu berfungsi, misalnya:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

output:

foobar is unset

sementara

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

output:

foobar is set
Stein Inge Morisbak
sumber
Harus menghapus [@] jika array memiliki lebih dari satu nilai.
Stein Inge Morisbak
1
Ini berfungsi dengan baik, selama Anda ingin menguji apakah itu memiliki nilai, bukan apakah itu sudah ditentukan. Yaitu, foobar=""kemudian akan melaporkan itu foobar is unset. Tidak tunggu, saya ambil kembali. Benar-benar hanya menguji apakah elemen pertama kosong atau tidak, jadi sepertinya hanya ide yang bagus jika Anda tahu variabel BUKAN array, dan Anda hanya peduli kekosongan, bukan ketajaman.
Ron Burk
hanya berfungsi jika skrip Anda berjalan dengan variabel yang tidak terdefinisi diizinkan (tanpa set -u)
Florian Heigl