Cara mengetahui apakah string tidak didefinisikan dalam skrip Bash shell


151

Jika saya ingin memeriksa string nol yang akan saya lakukan

[ -z $mystr ]

tetapi bagaimana jika saya ingin memeriksa apakah variabel telah didefinisikan sama sekali? Atau apakah tidak ada perbedaan dalam scripting Bash?


27
harap biasakan menggunakan [-z "$ mystr"] sebagai lawan dari [-z $ mystr]
Charles Duffy

bagaimana melakukan hal yang terbalik? Maksud saya ketika string tidak null
Buka jalan

1
@flow: Bagaimana dengan[ -n "${VAR+x}"] && echo not null
Lekensteyn

1
@CharlesDuffy Saya pernah membaca ini sebelumnya di banyak sumber online. Mengapa ini lebih disukai?
meraba

6
@ Ayos karena jika Anda tidak memiliki tanda kutip, konten variabel adalah string-split dan globbed; jika ini adalah string kosong, itu menjadi [ -z ]bukan [ -z "" ]; jika ia memiliki ruang, itu [ -z "my" "test" ]bukannya [ -z my test ]; dan jika itu [ -z * ], maka *diganti dengan nama-nama file di direktori Anda.
Charles Duffy

Jawaban:


138

Saya pikir jawaban Anda setelah tersirat (jika tidak dinyatakan) oleh Vinko 's jawaban , meskipun tidak terbilang sederhana. Untuk membedakan apakah VAR diatur tetapi kosong atau tidak disetel, Anda dapat menggunakan:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Anda mungkin dapat menggabungkan dua tes pada baris kedua menjadi satu dengan:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Namun, jika Anda membaca dokumentasi untuk Autoconf, Anda akan menemukan bahwa mereka tidak merekomendasikan menggabungkan istilah dengan ' -a' dan merekomendasikan menggunakan tes sederhana terpisah dikombinasikan dengan &&. Saya belum menemukan sistem di mana ada masalah; itu tidak berarti mereka tidak pernah ada (tetapi mereka mungkin sangat langka hari ini, bahkan jika mereka tidak langka di masa lalu).

Anda dapat menemukan detailnya, dan ekspansi parameter shell terkait lainnya , testatau[ perintah dan ekspresi kondisional dalam manual Bash.


Baru-baru ini saya ditanya melalui email tentang jawaban ini dengan pertanyaan:

Anda menggunakan dua tes, dan saya mengerti yang kedua dengan baik, tetapi bukan yang pertama. Lebih tepatnya saya tidak mengerti perlunya ekspansi variabel

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Bukankah ini akan mencapai hal yang sama?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Pertanyaan wajar - jawabannya adalah 'Tidak, alternatif Anda yang lebih sederhana tidak melakukan hal yang sama'.

Misalkan saya menulis ini sebelum ujian Anda:

VAR=

Tes Anda akan mengatakan "VAR tidak disetel sama sekali", tetapi tes saya akan mengatakan (dengan implikasi karena tidak menggemakan apa pun) "VAR diatur tetapi nilainya mungkin kosong". Coba skrip ini:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

Outputnya adalah:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

Dalam pasangan tes kedua, variabel diatur, tetapi diatur ke nilai kosong. Ini adalah perbedaan yang dibuat oleh ${VAR=value}dan ${VAR:=value}notasi. Ditto untuk ${VAR-value}dan ${VAR:-value}, dan ${VAR+value}dan ${VAR:+value}, dan seterusnya.


Sebagai Gili menunjukkan dalam bukunya jawaban , jika Anda menjalankan bashdengan set -o nounsetpilihan, maka jawaban dasar di atas gagal dengan unbound variable. Itu mudah diatasi:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Atau Anda dapat membatalkan set -o nounsetopsi dengan set +u( set -usetara dengan set -o nounset).


7
Satu-satunya masalah yang saya miliki dengan jawaban ini adalah bahwa ia menyelesaikan tugasnya dengan cara yang agak tidak langsung dan tidak jelas.
Swiss

3
Bagi mereka yang ingin mencari deskripsi tentang arti di atas di halaman bash man, lihat bagian "Parameter Ekspansi" dan kemudian untuk teks ini: "Ketika tidak melakukan ekspansi substring, menggunakan formulir yang didokumentasikan di bawah ini, uji bash untuk parameter yang tidak disetel atau null ['null' yang berarti string kosong]. Menghilangkan titik dua menghasilkan tes hanya untuk parameter yang tidak disetel (...) $ {parameter: + kata}: Gunakan Nilai Alternatif. Jika parameter adalah nol atau tidak disetel, tidak ada yang diganti, kalau tidak ekspansi kata akan diganti. "
David Tonhofer

2
@ Swiss Ini bukan tidak jelas atau tidak langsung, tetapi idiomatik. Mungkin bagi seorang programmer yang tidak terbiasa dengan ${+}dan ${-}tidak jelas, tetapi keakraban dengan konstruksi itu sangat penting jika seseorang ingin menjadi pengguna shell yang kompeten.
William Pursell

Lihat stackoverflow.com/a/20003892/14731 jika Anda ingin menerapkan ini dengan set -o nounsetdiaktifkan.
Gili

Saya melakukan banyak pengujian pada ini; karena hasil dalam skrip saya tidak konsisten. Saya sarankan rakyat melihat ke dalam " [ -v VAR ]" pendekatan dengan pesta -s v4.2 dan di luar .
Akan

38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z berfungsi untuk variabel yang tidak terdefinisi juga. Untuk membedakan antara yang tidak terdefinisi dan yang didefinisikan, Anda akan menggunakan hal-hal yang tercantum di sini atau, dengan penjelasan yang lebih jelas, di sini .

Cara terbersih adalah menggunakan ekspansi seperti dalam contoh-contoh ini. Untuk mendapatkan semua opsi Anda, periksa bagian Ekspansi Parameter dari manual.

Kata alternatif:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Nilai default:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Tentu saja Anda akan menggunakan salah satu dari ini secara berbeda, menempatkan nilai yang Anda inginkan daripada 'nilai default' dan menggunakan ekspansi secara langsung, jika sesuai.


1
Tapi saya ingin membedakan antara apakah string "" atau belum pernah didefinisikan. Apakah itu mungkin?
Setjmp

1
jawaban ini memberi tahu bagaimana membedakan antara dua kasus itu; ikuti bash faq link yang disediakannya untuk diskusi lebih lanjut.
Charles Duffy

1
Saya menambahkannya setelah komentarnya, Charles
Vinko Vrsalovic

2
Cari "Parameter Expansion" di halaman bash man untuk semua "trik" ini. Misalnya $ {foo: -default} untuk menggunakan nilai default, $ {foo: = default} untuk menetapkan nilai default, $ {foo:? Pesan kesalahan} untuk menampilkan pesan kesalahan jika foo tidak disetel, dll.
Jouni K Seppänen

18

Panduan skrip bash tingkat lanjut, 10.2. Substitusi Parameter:

  • $ {var + blahblah}: jika var didefinisikan, 'blahblah' diganti dengan ekspresi, kalau tidak null diganti
  • $ {var-blahblah}: jika var didefinisikan, itu sendiri diganti, lain 'blahblah' diganti
  • $ {var? blahblah}: jika var didefinisikan, ia diganti, kalau tidak fungsi tersebut ada dengan 'blahblah' sebagai pesan kesalahan.


untuk mendasarkan logika program Anda pada apakah variabel $ mystr didefinisikan atau tidak, Anda dapat melakukan hal berikut:

isdefined=0
${mystr+ export isdefined=1}

sekarang, jika isdefined = 0 maka variabel tidak terdefinisi, jika isdefined = 1 variabel didefinisikan

Cara memeriksa variabel ini lebih baik daripada jawaban di atas karena lebih elegan, mudah dibaca, dan jika bash shell Anda dikonfigurasi untuk kesalahan pada penggunaan variabel yang tidak ditentukan (set -u), skrip akan berakhir sebelum waktunya.


Hal berguna lainnya:

untuk memiliki nilai default 7 ditugaskan ke $ mystr jika itu tidak terdefinisi, dan biarkan utuh jika tidak:

mystr=${mystr- 7}

untuk mencetak pesan kesalahan dan keluar dari fungsi jika variabel tidak terdefinisi:

: ${mystr? not defined}

Berhati-hatilah di sini bahwa saya menggunakan ':' agar tidak memiliki isi $ mystr dieksekusi sebagai perintah jika itu didefinisikan.


Saya tidak tahu tentang itu? sintaks untuk variabel BASH. Itu tangkapan yang bagus.
Swiss

Ini bekerja juga dengan referensi variabel tidak langsung. Ini melewati: var= ; varname=var ; : ${!varname?not defined}dan ini berakhir: varname=var ; : ${!varname?not defined}. Tetapi itu adalah kebiasaan yang baik untuk digunakan set -uyang melakukan hal yang sama lebih mudah.
ceving

11

Ringkasan tes.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

Latihan yang bagus dalam bash. Terkadang path file memiliki spasi, atau untuk melindungi terhadap injeksi perintah
k107

1
Saya pikir dengan ${var+x}itu tidak perlu kecuali jika nama variabel diperbolehkan memiliki ruang di dalamnya.
Anne van Rossum

Bukan hanya praktik yang baik; itu diperlukan dengan [untuk mendapatkan hasil yang benar. Jika vartidak disetel atau kosong, [ -n $var ]kurangi [ -n ]yang memiliki status keluar 0, sementara [ -n "$var" ]memiliki status keluar yang diharapkan (dan benar) dari 1.
chepner

5

https://stackoverflow.com/a/9824943/14731 berisi jawaban yang lebih baik (jawaban yang lebih mudah dibaca dan berfungsi dengan set -o nounsetdiaktifkan). Ini kira-kira berfungsi seperti ini:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

Cara eksplisit untuk memeriksa variabel yang didefinisikan adalah:

[ -v mystr ]

1
Tidak dapat menemukan ini di halaman manual mana pun (untuk bash atau tes) tetapi itu berfungsi untuk saya .. Tahu versi apa ini ditambahkan?
Ash Berlin-Taylor

1
Ini didokumentasikan dalam Ekspresi Bersyarat , dirujuk dari testoperator di ujung ekor bagian Bourne Shell Builtins . Saya tidak tahu kapan itu ditambahkan, tetapi tidak dalam versi Apple bash(berdasarkan bash3.2.51), jadi mungkin fitur 4.x.
Jonathan Leffler

1
Ini tidak bekerja untuk saya GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). Kesalahannya adalah -bash: [: -v: unary operator expected. Per komentar di sini , membutuhkan minimal 4.2 bash untuk bekerja.
Acumenus

Terima kasih telah memeriksa kapan tersedia. Saya telah menggunakannya sebelumnya pada berbagai sistem, tetapi kemudian tiba-tiba itu tidak berfungsi. Saya datang ke sini karena penasaran dan ya, tentu saja bash pada sistem ini adalah bash 3.x.
clacke

1

pilihan lain: ekspansi "daftar indeks daftar" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

satu-satunya saat ini berkembang ke string kosong adalah ketika footidak disetel, sehingga Anda dapat memeriksanya dengan string bersyarat:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

harus tersedia dalam versi bash apa pun> = 3.0


1

bukan untuk menumpahkan motor ini lebih jauh, tetapi ingin menambahkan

shopt -s -o nounset

adalah sesuatu yang dapat Anda tambahkan ke bagian atas skrip, yang akan kesalahan jika variabel tidak dideklarasikan di mana pun dalam skrip. Pesan yang Anda lihat adalah unbound variable, tetapi seperti yang disebutkan orang lain, pesan itu tidak akan menangkap string kosong atau nilai nol. Untuk memastikan nilai individual apa pun tidak kosong, kami dapat menguji variabel yang diperluas dengan ${mystr:?}, juga dikenal sebagai ekspansi tanda dolar, yang akan salah parameter null or not set.


1

The manual Bash Reference merupakan sumber otoritatif informasi tentang bash.

Berikut adalah contoh pengujian suatu variabel untuk melihat apakah ada:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(Dari bagian 6.3.2 .)

Perhatikan bahwa spasi putih setelah terbuka [dan sebelum ]itu tidak opsional .


Kiat untuk pengguna Vim

Saya memiliki skrip yang memiliki beberapa deklarasi sebagai berikut:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Tetapi saya ingin mereka tunduk pada nilai-nilai yang ada. Jadi saya menulis ulang mereka agar terlihat seperti ini:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Saya dapat mengotomatisasi ini dalam vim menggunakan regex cepat:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Ini bisa diterapkan dengan memilih garis yang relevan secara visual, lalu mengetik :. Bilah perintah sudah diisi sebelumnya :'<,'>. Rekatkan perintah di atas dan tekan enter.


Diuji pada versi Vim ini:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Pengguna Windows mungkin menginginkan akhir baris yang berbeda.


0

Inilah yang menurut saya merupakan cara yang jauh lebih jelas untuk memeriksa apakah suatu variabel didefinisikan:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Gunakan sebagai berikut:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
Menulis fungsi bukanlah ide yang buruk; memohon grepitu buruk. Perhatikan bahwa bashdukungan ${!var_name}sebagai cara untuk mendapatkan nilai dari variabel yang namanya ditentukan $var_name, jadi name=value; value=1; echo ${name} ${!name}hasilkan value 1.
Jonathan Leffler

0

Versi yang lebih pendek untuk menguji variabel yang tidak terdefinisi dapat berupa:

test -z ${mystr} && echo "mystr is not defined"

-3

set panggilan tanpa argumen .. output semua vars yang ditentukan ..
yang terakhir dalam daftar akan menjadi yang didefinisikan dalam skrip Anda ..
sehingga Anda dapat menyalurkan outputnya ke sesuatu yang dapat mengetahui hal-hal apa yang didefinisikan dan apa yang tidak


2
Saya tidak memilih ini, tapi itu adalah godam untuk memecahkan kacang yang agak kecil.
Jonathan Leffler

Saya sebenarnya menyukai jawaban ini lebih baik daripada jawaban yang diterima. Jelas apa yang dilakukan dalam jawaban ini. Jawaban yang diterima tidak masuk akal.
Swiss

Sepertinya jawaban ini lebih merupakan efek samping dari implementasi "set" daripada sesuatu yang diinginkan.
Jonathan Morgan
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.