Periksa apakah array kosong di Bash


110

Saya memiliki array yang diisi dengan pesan kesalahan yang berbeda saat skrip saya berjalan.

Saya perlu cara untuk memeriksa apakah kosong atau tidak pada akhir skrip dan mengambil tindakan tertentu jika itu.

Saya sudah mencoba memperlakukannya seperti VAR normal dan menggunakan -z untuk memeriksanya, tetapi itu sepertinya tidak berhasil. Apakah ada cara untuk memeriksa apakah array kosong atau tidak di Bash?

Jawaban:


143

Misalkan array Anda adalah $errors, cukup periksa untuk melihat apakah jumlah elemen adalah nol.

if [ ${#errors[@]} -eq 0 ]; then
    echo "No errors, hooray"
else
    echo "Oops, something went wrong..."
fi

10
Harap dicatat bahwa itu =adalah operator string. Kebetulan berfungsi dengan baik dalam kasus ini, tetapi saya akan menggunakan operator aritmatika yang tepat -eqsebagai gantinya (kalau-kalau saya ingin beralih ke -geatau -lt, dll.).
musiphil

6
Tidak berfungsi dengan set -u: "variabel tidak terikat" - jika array kosong.
Igor

@Igor: Bekerja untuk saya di Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Jika saya unset foo, maka ia mencetak foo: unbound variable, tetapi itu berbeda: variabel array tidak ada sama sekali, bukan yang ada dan kosong.
Peter Cordes

Juga diuji dalam Bash 3.2 (OSX) saat menggunakan set -u- selama Anda mendeklarasikan variabel Anda terlebih dahulu, ini berfungsi dengan baik.
zeroimpl

15

Saya biasanya menggunakan ekspansi aritmatika dalam hal ini:

if (( ${#a[@]} )); then
    echo not empty
fi

Bagus dan bersih! Saya suka itu. Saya juga mencatat bahwa jika elemen pertama dari array selalu kosong, (( ${#a} ))(panjang elemen pertama) akan bekerja juga. Namun, itu akan gagal a=(''), sedangkan yang (( ${#a[@]} ))diberikan dalam jawaban akan berhasil.
cxw

8

Anda juga dapat mempertimbangkan array sebagai variabel sederhana. Dengan begitu, hanya menggunakan

if [ -z "$array" ]; then
    echo "Array empty"
else
    echo "Array non empty"
fi

atau menggunakan sisi lain

if [ -n "$array" ]; then
    echo "Array non empty"
else
    echo "Array empty"
fi

Masalah dengan solusi itu adalah bahwa jika sebuah array dideklarasikan seperti ini: array=('' foo). Pemeriksaan ini akan melaporkan array sebagai kosong, sementara jelas tidak. (terima kasih @musiphil!)

Menggunakan [ -z "$array[@]" ]jelas bukan solusi juga. Tidak menentukan kurung keriting mencoba menafsirkan $arraysebagai string ( [@]dalam hal ini string literal sederhana) dan karenanya selalu dilaporkan sebagai salah: "apakah string literal [@]kosong?" Jelas tidak.


7
[ -z "$array" ]atau [ -n "$array" ]tidak bekerja Coba array=('' foo); [ -z "$array" ] && echo empty, dan itu akan dicetak emptywalaupun arrayjelas tidak kosong.
musiphil

2
[[ -n "${array[*]}" ]]interpolasi seluruh array sebagai string, yang Anda periksa panjangnya tidak nol. Jika Anda menganggap array=("" "")kosong, daripada memiliki dua elemen kosong, ini mungkin berguna.
Peter Cordes

@PeterCordes Saya pikir itu tidak berhasil. Ekspresi dievaluasi menjadi karakter spasi tunggal, dan [[ -n " " ]]"benar," yang sangat disayangkan. Komentar Anda persis seperti yang ingin saya lakukan.
Michael

@Michael: Sial, kau benar. Ini hanya bekerja dengan array 1-elemen dari string kosong, bukan 2 elemen. Saya bahkan memeriksa bash yang lebih tua dan masih salah di sana; seperti yang Anda katakan set -xmenunjukkan bagaimana itu mengembang. Saya kira saya tidak menguji komentar itu sebelum memposting. >. <Anda dapat membuatnya bekerja dengan mengatur IFS=''(menyimpan / mengembalikannya di sekitar pernyataan ini), karena "${array[*]}"ekspansi memisahkan elemen dengan karakter pertama IFS. (Atau spasi jika tidak disetel). Tetapi " Jika IFS adalah nol, parameter digabungkan tanpa campur tangan pemisah. " (Docs untuk $ * posisi params, tapi saya anggap sama untuk array).
Peter Cordes

@Michael: Menambahkan jawaban yang melakukan ini.
Peter Cordes

3

Saya memeriksanya dengan bash-4.4.0:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]} ]]; then
        echo not empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

dan bash-4.1.5:

#!/usr/bin/env bash
set -eu
check() {
    if [[ ${array[@]:+${array[@]}} ]]; then
        echo non-empty
    else
        echo empty
    fi
}
check   # empty
array=(a b c d)
check   # not empty
array=()
check   # empty

Dalam kasus terakhir, Anda memerlukan konstruk berikut:

${array[@]:+${array[@]}}

agar tidak gagal pada array kosong atau tidak disetel. Itu jika Anda set -eusuka saya biasanya. Ini memberikan pemeriksaan kesalahan yang lebih ketat. Dari dokumen :

-e

Segera keluar jika saluran pipa (lihat Saluran Pipa), yang dapat terdiri dari satu perintah sederhana (lihat Perintah Sederhana), daftar (lihat Daftar), atau perintah gabungan (lihat Perintah Gabungan) mengembalikan status bukan nol. Shell tidak keluar jika perintah yang gagal adalah bagian dari daftar perintah segera setelah beberapa saat atau sampai kata kunci, bagian dari pengujian dalam pernyataan if, bagian dari perintah yang dieksekusi dalam && atau || daftar kecuali perintah yang mengikuti akhir && atau ||, perintah apa pun dalam pipa kecuali yang terakhir, atau jika status pengembalian perintah sedang dibalik dengan! Jika perintah majemuk selain subshell mengembalikan status tidak nol karena perintah gagal saat -e diabaikan, shell tidak keluar. Jebakan pada ERR, jika diatur, dieksekusi sebelum shell keluar.

Opsi ini berlaku untuk lingkungan shell dan setiap lingkungan subkulit secara terpisah (lihat Lingkungan Eksekusi Perintah), dan dapat menyebabkan subkulit keluar sebelum menjalankan semua perintah dalam subkulit.

Jika perintah majemuk atau fungsi shell dieksekusi dalam konteks di mana -e diabaikan, tidak ada perintah yang dieksekusi dalam perintah majemuk atau fungsi tubuh akan terpengaruh oleh pengaturan -e, bahkan jika -e diatur dan perintah mengembalikan a status kegagalan. Jika perintah majemuk atau fungsi shell menyetel -e saat mengeksekusi dalam konteks di mana -e diabaikan, pengaturan itu tidak akan berpengaruh sampai perintah gabungan atau perintah yang berisi panggilan fungsi selesai.

-u

Perlakukan variabel dan parameter yang tidak disetel selain parameter khusus '@' atau '*' sebagai kesalahan saat melakukan ekspansi parameter. Pesan kesalahan akan ditulis ke kesalahan standar, dan shell non-interaktif akan keluar.

Jika Anda tidak membutuhkannya, jangan ragu untuk menghilangkan :+${array[@]}bagiannya.

Perhatikan juga, bahwa penting untuk menggunakan [[operator di sini, dengan [Anda dapat:

$ cat 1.sh
#!/usr/bin/env bash
set -eu
array=(a b c d)
if [ "${array[@]}" ]; then
    echo non-empty
else
    echo empty
fi

$ ./1.sh
_/1.sh: line 4: [: too many arguments
empty

Dengan -uAnda harus benar-benar menggunakan ${array[@]+"${array[@]}"}cf stackoverflow.com/a/34361807/1237617
Jakub Bochenski

@JakubBochenski Versi bash mana yang Anda bicarakan? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

Masalah dalam contoh kurung tunggal adalah @, tentu saja. Anda bisa menggunakan *ekspansi array seperti [ "${array[*]}" ], bukan? Meski begitu, [[juga berfungsi dengan baik. Perilaku keduanya untuk array dengan banyak string kosong sedikit mengejutkan. Keduanya [ ${#array[*]} ]dan [[ "${array[@]}" ]]salah untuk array=()dan array=('')tetapi berlaku untuk array=('' '')(dua atau lebih string kosong). Jika Anda ingin satu atau lebih string kosong menjadi kenyataan, Anda dapat menggunakannya [ ${#array[@]} -gt 0 ]. Jika Anda ingin semuanya salah, Anda mungkin bisa //mengeluarkannya.
eisd

@ Eisd bisa saya gunakan [ "${array[*]}" ], tetapi jika saya mengalami ekspresi seperti itu, akan lebih sulit bagi saya untuk memahami apa fungsinya. Sejak [...]beroperasi dalam hal string pada hasil interpolasi. Berbeda dengan [[...]], yang bisa mewaspadai apa yang diinterpolasi. Artinya, bisa tahu bahwa itu dilewatkan array. [[ ${array[@]} ]]berbunyi bagi saya sebagai "periksa apakah array tidak kosong", sementara [ "${array[*]}" ]sebagai "periksa apakah hasil interpolasi semua elemen array adalah string yang tidak kosong".
x-yuri

... Adapun perilaku dengan dua string kosong itu sama sekali tidak mengejutkan bagi saya. Yang mengejutkan adalah perilaku dengan satu string kosong. Tapi bisa dibilang masuk akal. Mengenai [ ${#array[*]} ], Anda mungkin dimaksudkan [ "${array[*]}" ], karena yang pertama berlaku untuk sejumlah elemen. Karena jumlah elemen selalu non-string kosong. Mengenai yang terakhir dengan dua elemen, ekspresi di dalam tanda kurung berkembang menjadi ' 'string yang tidak kosong. Adapun [[ ${array[@]} ]], mereka hanya berpikir (dan memang demikian) bahwa setiap array dari dua elemen tidak kosong.
x-yuri

2

Jika Anda ingin mendeteksi array dengan elemen kosong , sepertiarr=("" "") kosong, sama denganarr=()

Anda dapat menempelkan semua elemen bersama dan memeriksa apakah hasilnya nol panjang. (Membangun salinan rata isi array tidak ideal untuk kinerja jika array bisa sangat besar. Tapi semoga Anda tidak menggunakan bash untuk program seperti itu ...)

Tetapi "${arr[*]}"berkembang dengan elemen yang dipisahkan oleh karakter pertama IFS. Jadi Anda perlu menyimpan / mengembalikan IFS dan lakukan IFS=''untuk membuat ini berfungsi, atau periksa apakah panjang string == # elemen array - 1. (Array nelemen memiliki n-1pemisah). Untuk mengatasinya secara off-by-one, akan lebih mudah untuk menggabungkannya dengan 1

arr=("" "")

## Assuming default non-empty IFS
## TODO: also check for ${#arr[@]} -eq 0
concat="${arr[*]} "      # n-1 separators + 1 space + array elements
[[ "${#concat}" -ne "${#arr[@]}" ]]  && echo not empty array || echo empty array

test case dengan set -x

### a non-empty element
$ arr=("" "x")
  + arr=("" "x")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat=' x '
  + [[ 3 -ne 2 ]]
  + echo not empty array
not empty array

### 2 empty elements
$ arr=("" "")
  + arr=("" "")
$ concat="${arr[*]} ";  [[ "${#concat}" -ne "${#arr[@]}" ]] && echo not empty array || echo empty array
  + concat='  '
  + [[ 2 -ne 2 ]]
  + echo empty array
empty array

Sayangnya ini gagal untuk arr=(): [[ 1 -ne 0 ]]. Jadi, Anda perlu memeriksa array yang benar-benar kosong terlebih dahulu secara terpisah.


Atau denganIFS='' . Mungkin Anda ingin menyimpan / mengembalikan IFS daripada menggunakan subkulit, karena Anda tidak bisa mendapatkan hasil dari subkulit dengan mudah.

# inside a () subshell so we don't modify our own IFS
(IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)

contoh:

$ arr=("" "")
$ (IFS='' ; [[ -n "${arr[*]}" ]] && echo not empty array || echo empty array)
   + IFS=
   + [[ -n '' ]]
   + echo empty array
empty array

tidak bekerja dengan arr=()- itu masih hanya string kosong.


Saya terbalik, tetapi saya sudah mulai menggunakan [[ "${arr[*]}" = *[![:space:]]* ]], karena saya dapat mengandalkan setidaknya satu karakter non-WS.
Michael

@Michael: ya, itu pilihan yang bagus jika Anda tidak perlu menolak arr=(" ").
Peter Cordes

0

Dalam kasus saya, Jawaban kedua tidak cukup karena mungkin ada ruang putih. Saya datang dengan:

if [ "$(echo -ne ${opts} | wc -m)" -eq 0 ]; then
  echo "No options"
else
  echo "Options found"
fi

echo | wctampaknya tidak efisien dibandingkan menggunakan shell built-in.
Peter Cordes

Tidak yakin jika saya memahami @PeterCordes, dapatkah saya mengubah jawaban kedua ' [ ${#errors[@]} -eq 0 ];dengan cara mengatasi masalah spasi putih? Saya juga lebih suka built-in.
Micha

Bagaimana sebenarnya ruang putih menyebabkan masalah? $#meluas ke angka, dan berfungsi dengan baik bahkan setelahnya opts+=(""). mis unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"dan saya dapatkan 2. Bisakah Anda menunjukkan contoh sesuatu yang tidak berhasil?
Peter Cordes

Sudah lama sekali. IIRC sumber yang berasal selalu dicetak setidaknya "". Jadi, untuk opts = "" atau opts = ("") saya perlu 0, bukan 1, mengabaikan baris baru yang kosong atau string kosong.
Micha

Oke, jadi Anda harus memperlakukan opts=("")yang sama opts=()? Itu bukan array kosong, tetapi Anda bisa memeriksa array kosong atau elemen pertama kosong opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Perhatikan bahwa jawaban Anda saat ini mengatakan "tidak ada opsi" untuk opts=("" "-foo"), yang benar-benar palsu, dan ini mereproduksi perilaku itu. Anda bisa [[ -z "${opts[*]}" ]]menebak, untuk menginterpolasi semua elemen array menjadi string datar, yang -zmemeriksa panjang tidak nol. Jika memeriksa elemen pertama sudah cukup, -z "$opts"berfungsi.
Peter Cordes

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.