Đếm số lượng phần tử trong mảng bash, trong đó tên của mảng là động (tức là được lưu trữ trong một biến)


11

Phát biểu ngắn gọn của câu hỏi:

Có phương pháp bash tích hợp để đếm số phần tử trong mảng bash không, trong đó tên của mảng là động (tức là được lưu trữ trong một biến), mà không cần phải tạo một bản sao đầy đủ của mảng hoặc sử dụng eval?

Thêm thông tin:

Sử dụng thay thế tham số bash, người ta có thể làm như sau:

  • Xác định độ dài của một mảng :
    myArr=(A B C); echo ${#myArr[@]}.
  • Tham chiếu gián tiếp một biến theo tên:
    NAME=myVar; echo ${!NAME}
    (điều này cũng áp dụng cho các phần tử mảng):
    NAME=myArr[1]; echo ${!NAME}

Nhưng nếu tên của một mảng được lưu trữ trong một biến khác, làm thế nào người ta có thể xác định số lượng phần tử trong mảng? (Người ta có thể coi đây là sự kết hợp của hai thay thế tham số ở trên.) Ví dụ:

myArr=(A B C D)
NAME=myArr
# Get the number of elements in the array indirectly referenced by NAME.
count=${#$NAME[@]}  # This syntax is invalid. What is the right way?

Dưới đây là nhiều nỗ lực mà tất cả FAIL:

  # Setup for following attempts:
  myArr=(A B C D)
  NAME=myArr
  EXPR1=$NAME[@]          # i.e. EXPR1='myArr[@]'
  EXPR2=#$NAME[@]         # i.e. EXPR2='#myArr[@]'

  # Failed attempts to get the lengh of the array indirectly:
  1.  count=${#$NAME[@]}  # ERROR: bash: ...: bad substitution
  2.  count=${#!EXPR1}    # ERROR: bash: !EXPR}: event not found
  3.  count=${#\!EXPR1}   # ERROR: bash: ...: bad substitution
  4.  count=${!#EXPR1}    # ERROR: bash: ...: bad substitution
  5.  count=${!EXPR2}     # Returns NULL

Tôi cũng đã thử một số biến thể khác ở trên, nhưng chưa tìm thấy bất cứ thứ gì hoạt động mà không có: (A) tạo một bản sao của mảng hoặc (B) bằng cách sử dụng eval.

Các phương pháp làm việc:

Có một số cách giải quyết vấn đề này có thể không tối ưu (nhưng hãy sửa tôi nếu tôi sai):

Phương pháp 1: Sao chép Mảng

Gán mảng cho biến khác (được đặt tên tĩnh) và lấy số phần tử trong đó.

EXPR=$NAME[@]
arrCopy=( "${!EXPR}" )
count=${#arrCopy}

Cách 2: Sử dụng eval

EXPR="count=\${#$NAME[@]}"  # i.e. 'count=${myArr[@]}'
eval $EXPR
# Now count is set to the length of the array

Tóm lược:

Có phương thức tích hợp nào (tức là cú pháp thay thế tham số) trong bash để xác định độ dài của một mảng một cách gián tiếp không? Nếu không, cách hiệu quả nhất để làm điều này là gì? Tôi giả sử đó là evalphương pháp trên, nhưng có vấn đề về bảo mật hoặc hiệu suất evalkhông?


2
Ừ Các biến lồng nhau. Tôi đã suy nghĩ lại bất cứ cách tiếp cận nào đưa tôi đến đây hơn là sử dụng các biến lồng nhau. Vấn đề thực sự ở đây là gì?
muru

1
Đó là một câu hỏi thú vị. Điều duy nhất tôi muốn cảnh báo bạn là giả sử một cái gì đó có hoặc không có vấn đề về hiệu suất. Tôi đã tìm thấy trong quá trình kiểm tra khá nghiêm ngặt để tối ưu hóa các tập lệnh bash rất lớn mà một số nội dung bash rất tệ về hiệu năng, trên thực tế, chỉ bằng cách xóa một bài kiểm tra khởi động trong một tập lệnh lớn, sử dụng những gì bạn có thể mong đợi là hiệu quả, tức là Trên thực tế, việc mở rộng biến đổi, dòng đơn đó đã làm chậm toàn bộ quá trình thực thi xuống khoảng 10 đến 20%. Phương pháp thử nghiệm trong các vòng lặp lớn với bộ hẹn giờ, kết quả có thể làm bạn ngạc nhiên.
Lizardx 10/11/2015

2
bash namerefs? . declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
iruvar

@muru - Đây chỉ là ngữ nghĩa, nhưng thuật ngữ "các biến lồng nhau" liên quan nhiều hơn đến bash trước phiên bản 2. Bash v2 đã thêm một cú pháp cho "tham chiếu biến gián tiếp". Tôi chỉ hỏi liệu có một cú pháp cụ thể để có được độ dài của một mảng được tham chiếu gián tiếp hay không. Tôi cho rằng các tác giả bash sẽ không nỗ lực triển khai các biến đổi vô hướng cho vô hướng mảng nếu đó không phải là một kỹ thuật hữu ích được yêu cầu - không chỉ đơn giản là một hack đảm bảo "Ugh" ngay lập tức, mặc dù tôi chắc chắn rằng điều đó gây tranh cãi .
drwatsoncode

1
Tôi đã làm một chút điểm chuẩn: time bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/nullvà tương tự như e=$c[@]; d=("${!e}); echo ${#d[@]}trong vòng lặp. Eval mất khoảng 90% thời gian thực hiện bằng cách sao chép. Và tôi cho rằng khoảng cách sẽ chỉ tăng mảng lớn hơn và các phần tử của nó.
muru

Câu trả lời:


4

bạn nên xử lý những thứ đó trong chỉ số evals. và bạn có thể gián tiếp thông qua các chỉ số của biến gián tiếp nếu bạn biến nó thành một mảng.

a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for   i in   0 1 2 3 4 5
do    c=
      printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
      printf "\n\tindex is $i and count is $c\n\n"
done

<abc1>

    index is 0 and count is 5

<def2>

    index is 1 and count is 5

<ghi3>

    index is 2 and count is 5

<jkl4>

    index is 3 and count is 5

<mno5>

    index is 4 and count is 5

<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>

    index is 5 and count is 5

bashcác chỉ mục dựa trên 0, nên tổng số đối tượng mảng sẽ luôn hoạt động nhiều hơn một chỉ số so với chỉ số được đặt cao nhất, và vì vậy:

c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"

this index is unset 5

... Tham số mở rộng ra từ mặc định nếu có được cung cấp.

Nếu không được cung cấp:

c=
${!r}
echo "$c"

5

... không có hại gì cả.

Trong vòng lặp tôi theo dõi một $ibiến ndex và kiểm tra xem nó có lớn nhất bằng $count không. Khi nó nhỏ hơn, tôi mở rộng $rvar eference a[i]vì nó là một chỉ số hợp lệ, nhưng khi nó bằng hoặc lớn hơn, tôi mở rộng $ref ra toàn bộ $array.

Đây là một chức năng:

ref_arr(){
    local    index=-1 count=
    local    ref=(   "$1[ count= \${#$1[@]}  ]"
                     "$1[ index ]"    "$1[ @ ]"
    )  &&    printf  "input array '%s' has '%d' members.\n" \
                     "$1"  "${!ref-${count:?invalid array name: "'$1'"}}"
    while    [ "$((index+=1))" -lt "$count"  ]
    do       printf  "$1[$index]  ==  '%s'\n"  "${!ref[1]}"
    done
}
some_array=(some "dumb
            stuff" 12345\'67890 "" \
          '$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'

input array 'some_array' has '5' members.
some_array[0]  ==  'some'
some_array[1]  ==  'dumb
                stuff'
some_array[2]  ==  '12345'67890'
some_array[3]  ==  ''
some_array[4]  ==  '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'


0

bash 4.3 namerefs là một ơn trời. Tuy nhiên, bạn có thể làm điều này:

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4

Cảm ơn bạn đã trả lời, nhưng câu trả lời của bạn là những gì tôi đã mô tả trong phần "Phương pháp 1: Sao chép Mảng". Câu hỏi cũng đặc biệt nêu rõ rằng độ dài của mảng phải được xác định "mà không cần dùng đến bản sao đầy đủ của mảng", đó chính xác là những gì bạn đã làm.
drwatson
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.