Bagaimana saya bisa menguji apakah suatu variabel kosong atau hanya berisi spasi?

240

Sintaks bash berikut memverifikasi jika paramtidak kosong:

 [[ !  -z  $param  ]]

Sebagai contoh:

param=""
[[ !  -z  $param  ]] && echo "I am not zero"

Tidak ada output dan baik-baik saja.

Tetapi ketika paramkosong kecuali untuk satu (atau lebih) karakter spasi, maka kasusnya berbeda:

param=" " # one space
[[ !  -z  $param  ]] && echo "I am not zero"

"Saya bukan nol" adalah output.

Bagaimana saya bisa mengubah tes untuk mempertimbangkan variabel yang hanya berisi karakter spasi sebagai kosong?

maihabunash
sumber
4
Dari man test: -z STRING - the length of STRING is zero. Jika Anda ingin menghapus semua spasi $param, gunakan${param// /}
dchirikov
6
WOW tidak ada trim()fungsi sederhana bawaan untuk * nix sama sekali? Begitu banyak peretasan yang berbeda untuk mencapai sesuatu yang sangat sederhana ...
ADTC
6
@ADTC $ (sed / s / ^ \ s + | \ s + $ // g '<<< $ string) tampaknya cukup sederhana bagi saya. Mengapa menemukan kembali roda?
jbowman
3
Karena itu bisa lebih bersinar
Inversus
1
Hanya mencatat bahwa [[ ! -z $param ]]itu setara dengan test ! -z $param.
Noah Sussman

Jawaban:

292

Pertama, perhatikan bahwa -ztes ini secara eksplisit untuk:

panjang senar adalah nol

Artinya, string yang hanya berisi spasi tidak boleh benar di bawah -z, karena memiliki panjang non-nol.

Yang Anda inginkan adalah menghapus spasi dari variabel menggunakan ekspansi parameter penggantian pola :

[[ -z "${param// }" ]]

Ini memperluas paramvariabel dan mengganti semua kecocokan pola (satu spasi) dengan apa-apa, sehingga string yang hanya memiliki spasi di dalamnya akan diperluas ke string kosong.


The seluk-beluk bagaimana yang bekerja adalah bahwa ${var/pattern/string}menggantikan pertandingan terpanjang pertama patterndengan string. Ketika patterndimulai dengan /(seperti di atas) maka itu akan menggantikan semua kecocokan. Karena penggantinya kosong, kami dapat menghilangkan final /dan stringnilainya:

$ {parameter / pola / string}

The pola diperluas untuk menghasilkan pola seperti dalam ekspansi nama file. Parameter diperluas dan pencocokan pola terpanjang terhadap nilainya diganti dengan string . Jika pola dimulai dengan '/', semua kecocokan pola diganti dengan string . Biasanya hanya pertandingan pertama yang diganti. ... Jika string adalah nol, kecocokan pola dihapus dan / mengikuti pola ini dapat dihilangkan.

Setelah semua itu, kami akhirnya ${param// }menghapus semua spasi.

Perhatikan bahwa meskipun ada di ksh(tempat asalnya), zshdan bash, sintaks itu bukan POSIX dan tidak boleh digunakan dalam shskrip.

Michael Homer
sumber
1
gunakan sedkata mereka ... hanya echovariabel yang mereka katakan ...
mikezter
1
Hmm. Jika demikian, ${var/pattern/string}bukankah seharusnya [[ -z ${param/ /} ]]dengan ruang antara garis miring menjadi polanya dan tidak ada apa-apanya menjadi tali?
Jesse Chisholm
@JesseChisholm "Karena penggantinya kosong, kita dapat menghilangkan nilai final / dan string"; "Jika string adalah nol, kecocokan pola dihapus dan / pola berikut ini dapat dihilangkan."
Michael Homer
6
@MichaelHomer Jawabannya [[ -z "${param// }" ]]pasti terlihat seperti patternkosong dan replacement stringsatu spasi. AH! Saya melihatnya sekarang if pattern begins with a slashsaya melewatkan bagian itu. begitu polanya / dan senarnya ditinggalkan dan oleh karena itu kosong. Terima kasih.
Jesse Chisholm
bash note: jika berjalan di set -o nounset (set -u), dan param tidak disetel (bukan null atau diisi-ruang), maka ini akan menghasilkan kesalahan variabel tidak terikat. (set + u; .....) akan mengecualikan tes ini.
Brian Chrisman
31

Cara mudah untuk memeriksa bahwa string hanya berisi karakter dalam set yang diotorisasi adalah untuk menguji keberadaan karakter yang tidak sah. Jadi, alih-alih menguji apakah string hanya berisi spasi, uji apakah string berisi beberapa karakter selain spasi. Dalam bash, ksh atau zsh:

if [[ $param = *[!\ ]* ]]; then
  echo "\$param contains characters other than space"
else
  echo "\$param consists of spaces only"
fi

“Terdiri dari spasi saja” termasuk case dari variabel kosong (atau tidak disetel).

Anda mungkin ingin menguji karakter spasi apa saja. Gunakan [[ $param = *[^[:space:]]* ]]untuk menggunakan pengaturan lokal, atau daftar karakter spasi kosong apa pun yang ingin Anda uji, misalnya [[ $param = *[$' \t\n']* ]]untuk menguji ruang, tab, atau baris baru.

Mencocokkan string dengan pola =di dalam [[ … ]]adalah ekstensi ksh (juga ada dalam bash dan zsh). Dalam gaya Bourne / POSIX apa pun, Anda bisa menggunakan casekonstruk untuk mencocokkan string dengan suatu pola. Perhatikan bahwa pola shell standar digunakan !untuk meniadakan set karakter, daripada ^seperti dalam kebanyakan sintaks ekspresi reguler.

case "$param" in
  *[!\ ]*) echo "\$param contains characters other than space";;
  *) echo "\$param consists of spaces only";;
esac

Untuk menguji karakter spasi, $'…'sintaks khusus untuk ksh / bash / zsh; Anda dapat memasukkan karakter-karakter ini dalam skrip Anda secara harfiah (perhatikan bahwa baris baru harus berada dalam tanda kutip, karena garis miring terbalik + baris baru tidak meluas), atau menghasilkannya, misalnya

whitespace=$(printf '\n\t ')
case "$param" in
  *[!$whitespace]*) echo "\$param contains non-whitespace characters";;
  *) echo "\$param consists of whitespace only";;
esac
Gilles
sumber
Ini hampir luar biasa - tetapi, sampai sekarang, itu tidak menjawab pertanyaan seperti yang ditanyakan. Saat ini ia dapat memberi tahu Anda perbedaan antara variabel shell yang tidak disetel atau kosong dan variabel yang hanya berisi spasi. Jika Anda akan menerima saran saya, Anda akan menambahkan formulir ekspansi param yang belum dikonversikan oleh jawaban lain yang secara eksplisit menguji variabel shell untuk ditetapkan - maksud saya ${var?not set}- melewati tes itu dan bertahan akan memastikan bahwa variabel yang cocok dengan Anda *) caseakan mempengaruhi definitif jawab, misalnya.
mikeserv
Koreksi - ini, pada kenyataannya, akan menjawab pertanyaan yang awalnya diajukan, tetapi sejak itu telah diedit sedemikian rupa sehingga secara fundamental mengubah makna pertanyaan dan karenanya membatalkan jawaban Anda.
mikeserv
Anda perlu [[ $param = *[!\ ]* ]]di mksh.
Stéphane Chazelas
20

POSIXly:

case $var in
  (*[![:blank:]]*) echo '$var contains non blank';;
  (*) echo '$var contains only blanks or is empty or unset'
esac

Untuk membedakan antara kosong , tidak kosong , kosong , tidak disetel :

case ${var+x$var} in
  (x) echo empty;;
  ("") echo unset;;
  (x*[![:blank:]]*) echo non-blank;;
  (*) echo blank
esac

[:blank:]adalah untuk karakter spasi horizontal (spasi dan tab di ASCII, tetapi mungkin ada beberapa lagi di lokal Anda; beberapa sistem akan menyertakan ruang tanpa-putus (jika tersedia), beberapa tidak akan). Jika Anda juga ingin karakter spasi vertikal (seperti baris baru atau umpan formulir), ganti [:blank:]dengan [:space:].

Stéphane Chazelas
sumber
POSIXly sangat ideal karena akan bekerja dengan tanda hubung (bisa dibilang lebih baik).
Ken Sharp
zshtampaknya memiliki masalah dengan [![:blank:]], itu meningkatkan :bpengubah tidak valid. Dalam shdan kshmeniru, itu meningkatkan acara yang tidak ditemukan untuk[
cuonglm
@cuonglm, apa yang Anda coba? var=foo zsh -c 'case ${var+x$var} in (x*[![:blank:]]*) echo x; esac'tidak apa-apa untuk saya. Acara tidak ditemukan hanya untuk shell interaktif.
Stéphane Chazelas
@ StéphaneChazelas: Ah, benar, saya mencoba dengan kerang interaktif. The :bpengubah valid agak aneh.
cuonglm
1
@FranklinYu, pada OS / X shadalah bashdibangun dengan --enable-xpg-echo-defaultdan --enable-strict-posix-defaultsehingga menjadi Unix compliant (yang melibatkan echo '\t'keluaran tab).
Stéphane Chazelas
4

Satu-satunya alasan yang tersisa untuk menulis skrip shell, alih-alih skrip dalam bahasa skrip yang baik , adalah jika portabilitas ekstrem menjadi perhatian utama. Warisan /bin/shadalah satu-satunya hal yang bisa Anda yakini miliki, tetapi Perl misalnya lebih mungkin tersedia lintas platform daripada Bash. Oleh karena itu, jangan pernah menulis skrip shell yang menggunakan fitur yang tidak benar-benar universal - dan perlu diingat bahwa beberapa vendor Unix miliknya membekukan lingkungan shell mereka sebelum POSIX.1-2001 .

Ada cara portabel untuk melakukan tes ini, tetapi Anda harus menggunakan tr:

[ "x`printf '%s' "$var" | tr -d "$IFS"`" = x ]

(Nilai default $IFS, mudahnya, adalah spasi, tab, dan baris baru.)

( printfBuiltin itu sendiri portabel, tetapi mengandalkan itu jauh lebih mudah daripada mencari tahu varian yang echoAnda miliki.)

zwol
sumber
1
Itu agak berbelit-belit. Cara portabel yang biasa untuk menguji apakah string berisi karakter tertentu adalah dengancase .
Gilles
@Gilles Saya rasa tidak mungkin menulis caseglob untuk mendeteksi string yang kosong, atau hanya berisi karakter spasi putih. (Itu akan mudah dengan regexp, tetapi casetidak melakukan regexps. Konstruk dalam jawaban Anda tidak akan berfungsi jika Anda peduli dengan baris baru.)
zwol
1
Saya memberi spasi sebagai contoh dalam jawaban saya karena itulah pertanyaan yang diajukan. Ini sama mudah untuk menguji karakter spasi: case $param in *[![:space:]]*) …. Jika Anda ingin menguji IFSkarakter, Anda dapat menggunakan polanya *[$IFS]*.
Gilles
1
@ mikeserv Dalam skrip defensif yang benar-benar serius , Anda secara eksplisit mengatur IFS <space><tab><newline>di awal dan kemudian tidak pernah menyentuhnya lagi. Saya pikir itu akan menjadi gangguan.
zwol
1
Mengenai variabel dalam pola, saya pikir itu berfungsi di semua versi Bourne dan semua shell POSIX; itu akan memerlukan kode tambahan untuk mendeteksi pola kasus dan mematikan ekspansi variabel dan saya tidak bisa memikirkan alasan untuk melakukannya. Saya punya beberapa contoh di saya .profileyang telah dieksekusi oleh banyak peluru Bourne. Tentu saja [$IFS]tidak akan melakukan apa yang dimaksudkan jika IFSmemiliki nilai-nilai non-default tertentu (kosong (atau tidak disetel), berisi ], dimulai dengan !atau ^).
Gilles
4
if [[ -n "${variable_name/[ ]*\n/}" ]]
then
    #execute if the the variable is not empty and contains non space characters
else
    #execute if the variable is empty or contains only spaces
fi
Guru
sumber
ini menghilangkan karakter spasi putih, bukan hanya satu spasi, kan? terima kasih
Alexander Mills
0

Untuk menguji apakah variabel kosong atau mengandung spasi, Anda juga dapat menggunakan kode ini:

${name:?variable is empty}
pratik
sumber
2
Saya pikir jawaban Anda dibatalkan karena "atau mengandung spasi" tidak benar.
Bernhard
hati-hati - yang membunuh shell saat ini. Lebih baik memasukkannya ke dalam (: $ {subshell?}). Juga - yang akan menulis ke stderr - Anda mungkin ingin mengarahkan ulang. Dan yang paling penting yang mengevaluasi nilai aktual variabel jika tidak disetel atau nol. Jika ada perintah di sana, Anda hanya menjalankannya. Yang terbaik untuk menjalankan tes itu :.
mikeserv
bagaimana itu akan membunuh shell. dan Anda juga dapat menguji ini, pertama-tama tentukan name=aaaakemudian jalankan perintah echo ${name:?variable is empty}ini akan mencetak nilai nama variabel dan sekarang jalankan perintah ini echo ${name1:?variable is empty}ia akan mencetak -sh: name1: variabel kosong
pratik
Ya - echo ${name:?variable is empty}akan mengevaluasi $namenilai bukan nol atau akan mematikan shell. Jadi untuk alasan yang sama Anda tidak melakukannya prompt > $randomvarjuga tidak boleh ${var?}- jika ada perintah di sana, itu mungkin akan berjalan - dan Anda tidak tahu apa itu. Shell interaktif tidak harus keluar - itulah sebabnya Anda tidak perlu keluar. Namun, jika Anda ingin melihat beberapa tes, ada banyak tes di sini. . Yang ini tidak cukup lama ...
mikeserv
0

Untuk menguji apakah suatu variabel tidak disetel, kosongkan (memiliki nilai nol) atau hanya berisi spasi:

 [ -z "${param#"${param%%[! ]*}"}" ] && echo "empty"

Penjelasan:

  • Perluasan ${param%%[! ]*}pemindahan dari non-ruang pertama ke akhir.
  • Itu hanya menyisakan ruang terkemuka.
  • Ekspansi berikutnya menghilangkan spasi utama dari variabel.
  • Jika hasilnya kosong, maka variabel itu kosong untuk memulai.
  • Atau, jika variabel itu tidak kosong, menghapus spasi memimpin membuatnya kosong.
  • Jika hasilnya kosong -z, maka gema empty.

Di atas adalah POSIX yang kompatibel dan berjalan di keturunan Bourne.

Jika Anda ingin memperluas itu juga tab, sertakan tab eksplisit:

 [ -z "${param#"${param%%[!     ]*}"}" ] && echo "empty"

atau gunakan variabel dengan karakter yang perlu Anda hapus:

 var=$' \t'   # in ksh, bash, zsh
 [ -z "${param#"${param%%[!$var]*}"}" ] && echo "empty"

atau Anda dapat menggunakan beberapa "ekspresi kelas karakter" POSIX (kosong berarti spasi dan tab):

 [ -z "${param#"${param%%[![:blank:]]*}"}" ] && echo "empty"

Tapi itu akan gagal di mksh, lksh dan posh.

Ishak
sumber
-1

Coba ini:

$ [[ -z \`echo $n\` ]] && echo zero

zero
Hugo
sumber