Kiểm tra biến là một mảng trong Bourne như shell?


14

Trong Bourne như shell hỗ trợ biến mảng, chúng ta có thể sử dụng một số phân tích cú pháp để kiểm tra xem biến có phải là một mảng không.

Tất cả các lệnh dưới đây đã được chạy sau khi chạy a=(1 2 3).

zsh:

$ declare -p a
typeset -a a
a=( 1 2 3 )

bash:

$ declare -p a
declare -a a='([0]="1" [1]="2" [2]="3")'

ksh93:

$ typeset -p a
typeset -a a=(1 2 3)

pdksh và đạo hàm của nó:

$ typeset -p a
set -A a
typeset a[0]=1
typeset a[1]=2
typeset a[2]=3

yash:

$ typeset -p a
a=('1' '2' '3')
typeset a

Một ví dụ trong bash:

if declare -p var 2>/dev/null | grep -q 'declare -a'; then
  echo array variable
fi

Cách tiếp cận này là quá nhiều công việc và cần phải sinh ra một subshell. Sử dụng các shell dựng sẵn khác như =~trong [[ ... ]]không cần một lớp con, nhưng vẫn quá phức tạp.

Có cách nào dễ dàng hơn để hoàn thành nhiệm vụ này?


Trong trường hợp nào bạn sẽ cần kiểm tra xem các biến của bạn có phải là mảng hay không?
Kusalananda

Câu trả lời:


10

Tôi không nghĩ bạn có thể, và tôi không nghĩ nó thực sự tạo ra sự khác biệt.

unset a
a=x
echo "${a[0]-not array}"

x

Điều đó cũng làm điều tương tự ở một trong hai ksh93bash. Có vẻ như tất cả các biến là các mảng trong các shell đó, hoặc ít nhất là bất kỳ biến thông thường nào chưa được gán thuộc tính đặc biệt, nhưng tôi đã không kiểm tra nhiều về điều đó.

Các bash cuộc đàm phán của nhãn hiệu về hành vi khác nhau cho một mảng so với một biến chuỗi khi sử dụng +=bài tập, nhưng nó sau hàng rào và các quốc gia mà các mảng chỉ cư xử khác nhau trong một hợp chất bối cảnh chuyển nhượng.

Nó cũng nói rằng một biến được coi là một mảng nếu bất kỳ chỉ mục con nào được gán một giá trị - và bao gồm rõ ràng khả năng của chuỗi null. Ở trên bạn có thể thấy rằng một bài tập thông thường chắc chắn dẫn đến một chỉ mục được gán - và vì vậy tôi đoán mọi thứ là một mảng.

Thực tế, có thể bạn có thể sử dụng:

[ 1 = "${a[0]+${#a[@]}}" ] && echo not array

... Để xác định chính xác các biến đặt chỉ được chỉ định một chỉ số con của giá trị 0.


Vì vậy, tôi đoán kiểm tra nếu ${a[1]-not array}có thể thực hiện nhiệm vụ, phải không?
cuonglm

@cuonglm - Vâng, không theo bashhướng dẫn: Một biến mảng được coi là thiết lập nếu một chỉ mục con đã được gán một giá trị. Chuỗi null là một giá trị hợp lệ. Nếu bất kỳ chỉ mục nào được chỉ định một mảng trên mỗi spec. Trong thực tế, cũng không, bởi vì bạn có thể làm a[5]=x. Tôi đoán [ 1 -eq "${#a[@]}" ] && [ -n "${a[0]+1}" ]có thể làm việc.
mikeerv

6

Vì vậy, bạn thực sự muốn chỉ là phần giữa của declare -p mà không có rác xung quanh nó?

Bạn có thể viết một macro như:

readonly VARTYPE='{ read __; 
       case "`declare -p "$__"`" in
            "declare -a"*) echo array;; 
            "declare -A"*) echo hash;; 
            "declare -- "*) echo scalar;; 
       esac; 
         } <<<'

để bạn có thể làm:

a=scalar
b=( array ) 
declare -A c; c[hashKey]=hashValue;
######################################
eval "$VARTYPE" a #scalar
eval "$VARTYPE" b #array
eval "$VARTYPE" c #hash

(Một hàm đơn thuần sẽ không làm nếu bạn muốn sử dụng hàm này cho các biến cục bộ-hàm).


Với bí danh

shopt -s expand_aliases
alias vartype='eval "$VARTYPE"'

vartype a #scalar
vartype b #array
vartype c #hash

@mikeerv Điểm tốt. Bí danh làm cho nó trông đẹp hơn. +1
PSkocik

ý tôi là - alias vartype="$VARTYPE"... hoặc chỉ không xác định $VARTYPEgì cả - nó sẽ hoạt động, phải không? bạn chỉ cần shoptđiều đó bashvì nó phá vỡ thông số kỹ thuật liên quan đến việc aliasmở rộng các tập lệnh.
mikeerv

1
@mikeerv Tôi chắc chắn cuonglm có khả năng điều chỉnh cách tiếp cận này theo nhu cầu và sở thích của anh ấy. ;-)
PSkocik

... và cân nhắc bảo mật.
PSkocik

Tại thời điểm không có mã trên eval người dùng cung cấp văn bản. Nó không kém an toàn hơn một chức năng. Tôi chưa bao giờ thấy bạn băn khoăn về việc làm cho các chức năng chỉ đọc, nhưng OK, tôi có thể đánh dấu biến chỉ đọc.
PSkocik

6

Trong zsh

zsh% a=(1 2 3) s=1
zsh% [[ ${(t)a} == *array* ]] && echo array
array
zsh% [[ ${(t)s} == *array* ]] && echo array
zsh%

Có lẽ echo ${(t)var}đơn giản hơn. Cảm ơn vì điều đó.

4

Để kiểm tra biến var, với

b=("${!var[@]}")
c="${#b[@]}"

Có thể kiểm tra nếu có nhiều hơn một chỉ mục mảng:

[[ $c > 1 ]] && echo "Var is an array"

Nếu giá trị chỉ mục đầu tiên không bằng 0:

[[ ${b[0]} -eq 0 ]] && echo "Var is an array"      ## should be 1 for zsh.

Sự nhầm lẫn khó khăn duy nhất là khi chỉ có một giá trị chỉ mục và giá trị đó bằng không (hoặc một).

Đối với điều kiện đó, có thể sử dụng hiệu ứng phụ của việc cố gắng loại bỏ một phần tử mảng khỏi một biến không phải là một mảng:

**bash** reports an error with             unset var[0]
bash: unset: var: not an array variable

**zsh** also reports an error with         $ var[1]=()
attempt to assign array value to non-array

Điều này hoạt động chính xác cho bash:

# Test if the value at index 0 could be unset.
# If it fails, the variable is not an array.
( unset "var[0]" 2>/dev/null; ) && echo "var is an array."

Đối với zsh, chỉ mục có thể cần là 1 (trừ khi chế độ tương thích được kích hoạt).

Vỏ phụ là cần thiết để tránh tác dụng phụ của việc xóa chỉ số 0 của var.

Tôi đã tìm thấy không có cách nào để làm cho nó hoạt động trong ksh.

Chỉnh sửa 1

Hàm này chỉ hoạt động trong bash4.2 +

getVarType(){
    varname=$1;
    case "$(typeset -p "$varname")" in
        "declare -a"*|"typeset -a"*)    echo array; ;;
        "declare -A"*|"typeset -A"*)    echo hash; ;;
        "declare -- "*|"typeset "$varname*| $varname=*) echo scalar; ;;
    esac;
}

var=( foo bar );  getVarType var

Chỉnh sửa 2

Điều này cũng chỉ hoạt động cho bash4.2 +

{ typeset -p var | grep -qP '(declare|typeset) -a'; } && echo "var is an array"

Lưu ý: Điều này sẽ cho kết quả dương tính giả nếu var chứa các chuỗi đã kiểm tra.


Làm thế nào về mảng với các phần tử bằng không?
cuonglm

1
Đạt chỉnh sửa, tho. Trông rất nguyên bản. : D
PSkocik

@cuonglm Kiểm tra ( unset "var[0]" 2>/dev/null; ) && echo "var is an array."báo cáo chính xác var là một mảng khi var đã được đặt thành var=()một mảng có các phần tử bằng không. Nó hoạt động chính xác bằng để tuyên bố.

Kiểm tra vô hướng sẽ không hoạt động nếu vô hướng được xuất hoặc đánh dấu số nguyên / chữ thường / chỉ đọc ... Bạn có thể chắc chắn làm cho nó bất kỳ đầu ra không trống nào khác có nghĩa là biến vô hướng. Tôi sẽ sử dụng grep -Ethay vì grep -Pđể tránh sự phụ thuộc vào GNU grep.
Stéphane Chazelas

@ StéphaneChazelas Bài kiểm tra (trong bash) cho vô hướng với số nguyên và / hoặc chữ thường và / hoặc chỉ đọc luôn bắt đầu bằng -a, như thế này : declare -airl var='()'. Do đó, kiểm tra grep sẽ hoạt động .

3

Đối với bash , đó là một chút hack (mặc dù được ghi lại): cố gắng sử dụng typesetđể xóa thuộc tính "mảng":

$ typeset +a BASH_VERSINFO
bash: typeset: BASH_VERSINFO: cannot destroy array variables in this way
echo $?
1

(Bạn không thể thực hiện việc này zsh, nó cho phép bạn chuyển đổi một mảng thành vô hướng, trong bashđó rõ ràng là bị cấm.)

Vì thế:

 typeset +A myvariable 2>/dev/null || echo is assoc-array
 typeset +a myvariable 2>/dev/null || echo is array

Hoặc trong một chức năng, lưu ý các cảnh báo ở cuối:

function typeof() {
    local _myvar="$1"
    if ! typeset -p $_myvar 2>/dev/null ; then
        echo no-such
    elif ! typeset -g +A  $_myvar 2>/dev/null ; then
        echo is-assoc-array
    elif ! typeset -g +a  $_myvar 2>/dev/null; then
        echo is-array
    else
        echo scalar
    fi
}

Lưu ý việc sử dụng typeset -g(bash-4.2 trở lên), điều này là bắt buộc trong một chức năng để typeset(đồng bộ declare) không hoạt động như thế localvà ghi đè lên giá trị bạn đang cố kiểm tra. Điều này cũng không xử lý các loại chức năng "biến", bạn có thể thêm một thử nghiệm chi nhánh khác bằng cách sử dụng typeset -fnếu cần.


Một tùy chọn khác (gần hoàn thành) là sử dụng:

    ${!name[*]}
          If name is an array variable, expands to  the  list
          of  array indices (keys) assigned in name.  If name
          is not an array, expands to 0 if name  is  set  and
          null  otherwise.   When @ is used and the expansion
          appears within double quotes, each key expands to a
          separate word.

Tuy nhiên, có một vấn đề nhỏ, một mảng có một chỉ số 0 phù hợp với hai trong số các điều kiện trên. Đây là điều mà mikeerv cũng tham khảo, bash thực sự không có sự phân biệt khó khăn và một số điều này (nếu bạn kiểm tra Changelog) có thể bị đổ lỗi cho ksh và tương thích với cách ${name[*]}hoặc${name[@]} hành xử trên một mảng không.

Vì vậy, một giải pháp một phần là:

if [[ ${!BASH_VERSINFO[*]} == '' ]]; then
    echo no-such
elif [[ ${!BASH_VERSINFO[*]} == '0' ]]; then 
    echo not-array
elif [[ ${!BASH_VERSINFO[*]} != '0' ]]; 
    echo is-array    
fi

Tôi đã sử dụng trong quá khứ một biến thể về điều này:

while read _line; do
   if [[ $_line =~ ^"declare -a" ]]; then 
     ...
   fi 
done < <( declare -p )

điều này cũng cần một subshell mặc dù.

Một kỹ thuật hữu ích hơn có thể là compgen:

compgen -A arrayvar

Điều này sẽ liệt kê tất cả các mảng được lập chỉ mục, tuy nhiên các mảng kết hợp không được xử lý đặc biệt (tối đa bash-4.4) và xuất hiện dưới dạng các biến thông thường ( compgen -A variable)


Các typeset +acũng báo cáo một lỗi trong ksh. Không phải trong zsh, mặc dù.

1

Câu trả lời ngắn:

Đối với hai hệ vỏ đã đưa ra ký hiệu này ( bashksh93) một biến vô hướng chỉ là một mảng với một phần tử duy nhất .

Không cần một tuyên bố đặc biệt để tạo ra một mảng. Chỉ cần phân công là đủ, và một nhiệm vụ đơn giản var=valuelà giống hệt var[0]=value.


Hãy thử : bash -c 'unset var; var=foo; typeset -p var'. Do bash answer báo cáo một mảng (cần một -a)?. Bây giờ so sánh với : bash -c 'unset var; var[12]=foo; typeset -p var'. Tại sao lại có một sự khác biệt?. Trả lời: Vỏ duy trì (tốt hoặc xấu) một khái niệm trong đó các vars là vô hướng hoặc mảng. Shell ksh làm trộn cả hai khái niệm thành một.

1

Nội arraytrang của yash có một số tùy chọn chỉ hoạt động với các biến mảng. Ví dụ: -dtùy chọn sẽ báo lỗi về biến không phải là mảng:

$ a=123
$ array -d a
array: no such array $a

Vì vậy, chúng ta có thể làm một cái gì đó như thế này:

is_array() (
  array -d -- "$1"
) >/dev/null 2>&1

a=(1 2 3)
if is_array a; then
  echo array
fi

b=123
if ! is_array b; then
  echo not array
fi

Cách tiếp cận này sẽ không hoạt động nếu biến mảng chỉ đọc . Cố gắng sửa đổi một biến chỉ đọc dẫn đến lỗi:

$ a=()
$ readonly a
$ array -d a
array: $a is read-only

0
#!/bin/bash

var=BASH_SOURCE

[[ "$(declare -pa)" =~ [^[:alpha:]]$var= ]]

case "$?" in 
  0)
      echo "$var is an array variable"
      ;;
  1)
      echo "$var is not an array variable"
      ;;
  *)
      echo "Unknown exit code"
      ;;
esac
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.