Kiểm tra xem mảng có trống trong Bash không


110

Tôi có một mảng chứa đầy các thông báo lỗi khác nhau khi tập lệnh của tôi chạy.

Tôi cần một cách để kiểm tra xem nó có trống không ở cuối tập lệnh hay không và thực hiện một hành động cụ thể nếu có.

Tôi đã thử xử lý nó như một VAR bình thường và sử dụng -z để kiểm tra nó, nhưng điều đó dường như không hiệu quả. Có cách nào để kiểm tra xem một mảng có trống hay không trong Bash?

Câu trả lời:


143

Giả sử mảng của bạn là $errors, chỉ cần kiểm tra xem số lượng phần tử có bằng không.

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

10
Xin lưu ý rằng đó =là một toán tử chuỗi. Nó hoạt động tốt trong trường hợp này, nhưng -eqthay vào đó tôi sẽ sử dụng toán tử số học thích hợp (chỉ trong trường hợp tôi muốn chuyển sang -gehoặc -lt, v.v.).
musiphil

6
Không hoạt động với set -u: "biến không liên kết" - nếu mảng trống.
Igor

@Igor: Hoạt động với tôi trong Bash 4.4. set -u; foo=(); [ ${#foo[@]} -eq 0 ] && echo empty. Nếu tôi unset foo, sau đó nó in foo: unbound variable, nhưng đó là khác nhau: biến mảng hoàn toàn không tồn tại, thay vì tồn tại và trống rỗng.
Peter Cordes

Cũng được thử nghiệm trong Bash 3.2 (OSX) khi sử dụng set -u- miễn là bạn khai báo biến của mình trước, điều này hoạt động hoàn hảo.
zeroimpl

15

Tôi thường sử dụng mở rộng số học trong trường hợp này:

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

Đẹp và sạch sẽ! Tôi thích nó. Tôi cũng lưu ý rằng nếu phần tử đầu tiên của mảng luôn không trống, (( ${#a} ))(độ dài của phần tử đầu tiên) cũng sẽ hoạt động. Tuy nhiên, điều đó sẽ thất bại a=(''), trong khi (( ${#a[@]} ))đưa ra trong câu trả lời sẽ thành công.
cxw

8

Bạn cũng có thể coi mảng là một biến đơn giản. Theo cách đó, chỉ cần sử dụng

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

hoặc sử dụng mặt khác

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

Vấn đề với giải pháp đó là nếu một mảng được khai báo như thế này : array=('' foo). Các kiểm tra này sẽ báo cáo mảng là trống, trong khi rõ ràng là không. (cảm ơn @musiphil!)

Sử dụng [ -z "$array[@]" ]rõ ràng không phải là một giải pháp. Không chỉ định dấu ngoặc nhọn cố gắng diễn giải $arraythành một chuỗi ( [@]trong trường hợp đó là một chuỗi ký tự đơn giản) và do đó luôn được báo cáo là sai: "chuỗi ký tự có [@]trống không?" Rõ ràng không.


7
[ -z "$array" ]hoặc [ -n "$array" ]không hoạt động. Hãy thử array=('' foo); [ -z "$array" ] && echo empty, và nó sẽ in emptymặc dù arrayrõ ràng là không trống.
musiphil

2
[[ -n "${array[*]}" ]]nội suy toàn bộ mảng dưới dạng một chuỗi mà bạn kiểm tra độ dài khác không. Nếu bạn coi array=("" "")là trống, thay vì có hai phần tử trống, điều này có thể hữu ích.
Peter Cordes

@PeterCordes Tôi không nghĩ rằng nó hoạt động. Biểu thức ước tính cho một ký tự không gian duy nhất và [[ -n " " ]]là "đúng", thật đáng tiếc. Nhận xét của bạn là chính xác những gì tôi muốn làm.
Michael

@Michael: Crap, bạn nói đúng. Nó chỉ hoạt động với mảng 1 phần tử của một chuỗi rỗng, không phải 2 phần tử. Tôi thậm chí đã kiểm tra bash cũ hơn và nó vẫn sai ở đó; như bạn nói set -xcho thấy làm thế nào nó mở rộng. Tôi đoán tôi đã không kiểm tra bình luận đó trước khi đăng. >. <Bạn có thể làm cho nó hoạt động bằng cách cài đặt IFS=''(lưu / khôi phục nó xung quanh câu lệnh này), vì "${array[*]}"mở rộng tách biệt các phần tử với ký tự đầu tiên của IFS. (Hoặc không gian nếu không đặt). Nhưng " Nếu IFS là null, các tham số được nối mà không có dấu phân cách. " (Tài liệu cho tham số vị trí $ *, nhưng tôi giả sử tương tự cho mảng).
Peter Cordes

@Michael: Đã thêm một câu trả lời làm điều này.
Peter Cordes

3

Tôi đã kiểm tra nó với 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

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

Trong trường hợp sau, bạn cần cấu trúc sau:

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

để nó không thất bại trên mảng trống hoặc unset. Đó là nếu bạn làm set -eunhư tôi thường làm. Điều này cung cấp cho việc kiểm tra lỗi nghiêm ngặt hơn. Từ các tài liệu :

-e

Thoát ngay lập tức nếu một đường ống (xem Pipelines), có thể bao gồm một lệnh đơn giản (xem Lệnh đơn giản), danh sách (xem Danh sách) hoặc lệnh ghép (xem Lệnh gộp) trả về trạng thái khác không. Shell không thoát nếu lệnh bị lỗi là một phần của danh sách lệnh ngay sau một thời gian hoặc cho đến khi từ khóa, một phần của kiểm tra trong câu lệnh if, một phần của bất kỳ lệnh nào được thực thi trong && hoặc || liệt kê ngoại trừ lệnh theo lệnh cuối cùng && hoặc ||, bất kỳ lệnh nào trong đường ống ngoại trừ lệnh cuối cùng hoặc nếu trạng thái trả về của lệnh đang được đảo ngược với !. Nếu một lệnh ghép không phải là một lớp con trả về trạng thái khác không vì lệnh bị lỗi trong khi -e bị bỏ qua, shell sẽ không thoát. Một cái bẫy trên ERR, nếu được đặt, được thực thi trước khi thoát khỏi shell.

Tùy chọn này áp dụng cho môi trường shell và từng môi trường lớp con riêng biệt (xem Môi trường thực thi lệnh) và có thể khiến các lớp con thoát ra trước khi thực hiện tất cả các lệnh trong lớp con.

Nếu một lệnh ghép hoặc hàm shell thực thi trong ngữ cảnh trong đó -e bị bỏ qua, không có lệnh nào được thực thi trong lệnh ghép hoặc thân hàm sẽ bị ảnh hưởng bởi cài đặt -e, ngay cả khi -e được đặt và lệnh trả về tình trạng thất bại. Nếu một lệnh ghép hoặc hàm shell đặt -e trong khi thực thi trong ngữ cảnh trong đó -e bị bỏ qua, cài đặt đó sẽ không có bất kỳ ảnh hưởng nào cho đến khi lệnh ghép hoặc lệnh chứa lệnh gọi hàm hoàn thành.

-u

Xử lý các biến và tham số không đặt khác với các tham số đặc biệt '@' hoặc '*' là lỗi khi thực hiện mở rộng tham số. Một thông báo lỗi sẽ được ghi vào lỗi tiêu chuẩn và một vỏ không tương tác sẽ thoát.

Nếu bạn không cần điều đó, hãy bỏ qua :+${array[@]}một phần.

Cũng lưu ý rằng, điều cần thiết là sử dụng [[toán tử ở đây, với [bạn nhận được:

$ 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

Với -ubạn thực sự nên sử dụng ${array[@]+"${array[@]}"}cf stackoverflow.com/a
432361807/1237617

@JakubBochenski Bạn đang nói về phiên bản bash nào? gist.github.com/x-yuri/d933972a2f1c42a49fc7999b8d5c50b9
x-yuri

Vấn đề trong ví dụ ngoặc đơn là @, chắc chắn. Bạn có thể sử dụng *mở rộng mảng như thế [ "${array[*]}" ], bạn có thể không? Tuy nhiên, [[cũng hoạt động tốt. Hành vi của cả hai điều này đối với một mảng có nhiều chuỗi trống là một chút ngạc nhiên. Cả hai [ ${#array[*]} ][[ "${array[@]}" ]]đều sai cho array=()array=('')nhưng đúng cho array=('' '')(hai hoặc nhiều chuỗi trống). Nếu bạn muốn một hoặc nhiều chuỗi trống cho tất cả đều đúng, bạn có thể sử dụng [ ${#array[@]} -gt 0 ]. Nếu bạn muốn tất cả chúng sai, bạn có thể //loại bỏ chúng.
thẩm quyền

@eitor Tôi có thể sử dụng [ "${array[*]}" ], nhưng nếu tôi gặp phải biểu hiện như vậy, tôi sẽ khó hiểu hơn những gì nó làm. Vì [...]hoạt động dưới dạng các chuỗi trên kết quả của phép nội suy. Trái ngược với [[...]], có thể nhận thức được những gì đã được nội suy. Đó là, nó có thể biết rằng nó đã được thông qua một mảng. [[ ${array[@]} ]]đọc cho tôi là "kiểm tra xem mảng có trống không", trong khi [ "${array[*]}" ]"kiểm tra xem kết quả của phép nội suy của tất cả các phần tử mảng có phải là một chuỗi không trống" hay không.
x-yuri

... Đối với hành vi với hai chuỗi trống, nó hoàn toàn không gây ngạc nhiên cho tôi. Điều đáng ngạc nhiên là hành vi với một chuỗi trống. Nhưng được cho là hợp lý. Về [ ${#array[*]} ], có lẽ bạn có nghĩa là [ "${array[*]}" ], vì trước đây là đúng với bất kỳ số lượng các yếu tố. Bởi vì số phần tử luôn là chuỗi không rỗng. Về phần tử sau với hai phần tử, biểu thức bên trong ngoặc mở rộng thành ' 'chuỗi không rỗng. Đối với [[ ${array[@]} ]], họ chỉ nghĩ (và đúng như vậy) rằng bất kỳ mảng nào của hai phần tử là không trống.
x-yuri

2

Nếu bạn muốn phát hiện một mảng có các phần tử trống , nhưarr=("" "") trống, giống nhưarr=()

Bạn có thể dán tất cả các yếu tố lại với nhau và kiểm tra xem kết quả có độ dài bằng không. (Xây dựng một bản sao phẳng của nội dung mảng không lý tưởng cho hiệu suất nếu mảng có thể rất lớn. Nhưng hy vọng bạn không sử dụng bash cho các chương trình như vậy ...)

Nhưng "${arr[*]}"mở rộng với các yếu tố được phân tách bằng ký tự đầu tiên của IFS. Vì vậy, bạn cần lưu / khôi phục IFS và thực hiện IFS=''để thực hiện công việc này, nếu không thì kiểm tra xem độ dài chuỗi == # của các phần tử mảng - 1. (Một mảng các nphần tử có n-1dấu phân cách). Để giải quyết vấn đề đó, cách dễ nhất là ghép phần 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

trường hợp thử nghiệm với 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

Thật không may, điều này không thành công cho arr=(): [[ 1 -ne 0 ]]. Vì vậy, trước tiên bạn cần kiểm tra các mảng thực sự trống.


Hoặc vớiIFS='' . Có lẽ bạn muốn lưu / khôi phục IFS thay vì sử dụng một lớp con, bởi vì bạn không thể nhận được kết quả từ một lớp con một cách dễ dàng.

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

thí dụ:

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

không hoạt động với arr=()- nó vẫn chỉ là chuỗi rỗng.


Tôi đã nâng cấp, nhưng tôi đã bắt đầu sử dụng [[ "${arr[*]}" = *[![:space:]]* ]], vì tôi có thể tin tưởng vào ít nhất một ký tự không phải WS.
Michael

@Michael: yup, đó là một lựa chọn tốt nếu bạn không cần từ chối arr=(" ").
Peter Cordes

0

Trong trường hợp của tôi, Câu trả lời thứ hai là không đủ vì có thể có khoảng trắng. Tôi đã đến cùng với:

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

echo | wcdường như không hiệu quả so với việc sử dụng shell dựng sẵn.
Peter Cordes

Không chắc chắn nếu tôi hiểu @PeterCordes, tôi có thể sửa đổi câu trả lời thứ hai ' [ ${#errors[@]} -eq 0 ];theo cách khắc phục vấn đề khoảng trắng không? Tôi cũng thích tích hợp sẵn.
Micha

Làm thế nào chính xác không gian trắng gây ra một vấn đề? $#mở rộng đến một số và hoạt động tốt ngay cả sau đó opts+=(""). ví dụ unset opts; opts+=("");opts+=(" "); echo "${#opts[@]}"và tôi nhận được 2. Bạn có thể đưa ra một ví dụ về một cái gì đó không hoạt động?
Peter Cordes

Lâu lắm rồi. IIRC nguồn gốc luôn được in ít nhất "". Do đó, với opts = "" hoặc opts = ("") tôi cần 0, không phải 1, bỏ qua dòng mới hoặc chuỗi trống.
Micha

Ok, vậy bạn cần đối xử opts=("")như opts=()thế nào? Đó không phải là một mảng trống, nhưng bạn có thể kiểm tra mảng trống hoặc phần tử đầu tiên trống với opts=(""); [[ "${#opts[@]}" -eq 0 || -z "$opts" ]] && echo empty. Lưu ý rằng câu trả lời hiện tại của bạn nói "không có tùy chọn" nào opts=("" "-foo"), hoàn toàn không có thật và điều này tái tạo hành vi đó. Bạn có thể [[ -z "${opts[*]}" ]]đoán, để nội suy tất cả các phần tử mảng thành một chuỗi phẳng, -zkiểm tra độ dài khác không. Nếu kiểm tra phần tử đầu tiên là đủ, -z "$opts"hoạt động.
Peter Cordes

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.