2012-08-02 37 views
20

Sau khi tìm kiếm một cách để tạo một mảng liên kết trong bash, tôi thấy rằng declare -A array sẽ thực hiện thủ thuật. Nhưng vấn đề là, nó chỉ dành cho phiên bản bash 4 và phiên bản bash mà máy chủ có trong hệ thống của chúng tôi là 3.2.16.Tạo mảng liên kết trong bash 3

Làm cách nào để có thể đạt được một số loại hack giống như mảng liên kết trong bash 3? Các giá trị sẽ được chuyển đến một tập lệnh như

ARG=array[key]; 

./script.sh ${ARG} 

EDIT: Tôi biết rằng tôi có thể làm điều này trong các công cụ khác hoặc khó khăn nhưng cần phải giải quyết vấn đề.

+0

Vì bash 3 có mảng bình thường, bạn sẽ chỉ phải triển khai các mảng kết hợp dựa trên chúng. Nhưng nghiêm túc, tại sao điều đó lại cần thiết? –

+3

Vui lòng xem [BashFAQ/006] (http://mywiki.wooledge.org/BashFAQ/006). –

Trả lời

25

Bash 3 không có mảng liên kết, vì vậy bạn sẽ phải sử dụng một số tính năng ngôn ngữ khác cho mục đích của mình. Lưu ý rằng ngay cả dưới bash 4, mã bạn viết không làm những gì bạn yêu cầu: ./script.sh ${ARG} không chuyển mảng kết hợp vào tập lệnh con, vì ${ARG} mở rộng thành không có gì khi ARG là mảng liên kết. Bạn không thể vượt qua một mảng kết hợp với một tiến trình con, bạn cần phải mã hóa nó.

Bạn cần xác định một số giao thức truyền giao thức giữa tập lệnh mẹ và tập lệnh con. Một thông thường là chuyển các đối số trong biểu mẫu key=value. Điều này giả định rằng ký tự = không xuất hiện trong khóa.

Bạn cũng cần tìm ra cách trình bày mảng kết hợp trong tập lệnh mẹ và trong tập lệnh con. Họ không cần sử dụng cùng một biểu diễn.

Phương pháp phổ biến để biểu diễn mảng liên kết là sử dụng các biến riêng cho từng phần tử, với tiền tố đặt tên chung. Điều này đòi hỏi rằng tên khóa chỉ bao gồm các chữ cái ASCII (của cả hai trường hợp), chữ số và dấu gạch dưới. Ví dụ: thay vì ${myarray[key]}, hãy viết ${myarray__key}. Nếu khóa được xác định tại thời gian chạy, bạn cần một vòng mở rộng đầu tiên: thay vì ${myarray[$key]}, viết

n=myarray__${key}; echo ${!n} 

Đối với chuyển nhượng, sử dụng printf -v. Lưu ý định dạng %s thành printf để sử dụng giá trị được chỉ định. Không viết printf -v "myarray__${key}" %s "$value" vì điều đó sẽ xử lý $value dưới dạng định dạng và thực hiện mở rộng printf % mở rộng trên đó.

printf -v "myarray__${key}" %s "$value" 

Nếu bạn cần phải vượt qua một mảng kết hợp biểu diễn như thế này để một quá trình con với các đại diện key=value cãi nhau, bạn có thể sử dụng ${!myarray__*} liệt kê trên tất cả các biến có tên bắt đầu bằng myarray__.

args=() 
for k in ${!myarray__*}; do 
    n=$k 
    args+=("$k=${!n}") 
done 

Trong quá trình trẻ em, để chuyển đổi đối số có dạng key=value để biến tách với một tiền tố:

for x; do 
    if [[ $x != *=* ]]; then echo 1>&2 "KEY=VALUE expected, but got $x"; exit 120; fi 
    printf -v "myarray__${x%%=*}" %s "${x#*=}" 
done 

Bằng cách này, bạn có chắc rằng đây là những gì bạn cần? Thay vì gọi một tập lệnh bash từ tập lệnh bash khác, bạn có thể muốn chạy tập lệnh con trong một phiên bản con thay thế. Bằng cách đó nó sẽ kế thừa từ tất cả các biến của phụ huynh.

+1

Bạn có thể sử dụng 'printf -v' thay vì' declare' để tránh bản địa hóa biến trong hàm. Tìm nguồn gốc kịch bản con thay vì gọi nó cũng có thể là một cách để làm cho mảng của cha mẹ và các biến khác có sẵn cho đứa trẻ. –

+0

Công cụ tuyệt vời! –

+0

Tôi không hiểu tại sao bạn đặt backspaces trong 'for k in' '$ {! Myarray __ *}' '; do' Nó không phải là một lệnh. –

2

Bạn có thể ghi cặp khóa-giá trị vào tệp và sau đó grep theo khóa. Nếu bạn sử dụng một mô hình như

key=value 

sau đó bạn có thể egrep cho ^key= mà làm này khá an toàn.

Để "ghi đè" một giá trị, chỉ cần thêm giá trị mới vào cuối của tập tin và sử dụng tail -1 để có được chỉ là kết quả cuối cùng của egrep

Ngoài ra, bạn có thể đưa thông tin này vào một mảng bình thường sử dụng key=value làm giá trị cho mảng và sau đó lặp trên mảng để tìm giá trị.

6

Dưới đây là một bài/giải thích về mảng kết hợp trong bash 3 tuổi trở lên sử dụng mở rộng tham số: Phương pháp
https://stackoverflow.com/a/4444841

Gilles' có if tuyên bố tốt đẹp để bắt các vấn đề delimiter, khử trùng đầu vào kỳ quặc ... vv. Dùng nó.

Nếu bạn có phần quen thuộc với việc mở rộng tham số:
http://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html

Để sử dụng trong kịch bản của bạn [như đã nêu: gửi đến kịch bản]: Script 1: sending_array.sh

# A pretend Python dictionary with bash 3 
ARRAY=("cow:moo" 
     "dinosaur:roar" 
     "bird:chirp" 
     "bash:rock") 

bash ./receive_arr.sh "${ARRAY[@]}" 

Script 2: receive_arr.sh

argAry1=("[email protected]") 

function process_arr() { 
    declare -a hash=("${!1}") 
    for animal in "${hash[@]}"; do 
     echo "Key: ${animal%%:*}" 
     echo "Value: ${animal#*:}" 
    done 
} 

process_arr argAry1[@] 

exit 0 

Phương pháp 2, nguồn kịch bản thứ hai: Script 1: sending_array.sh

source ./receive_arr.sh 
# A pretend Python dictionary with bash 3 
ARRAY=("cow:moo" 
     "dinosaur:roar" 
     "bird:chirp" 
     "bash:rock") 

process_arr ARRAY[@] 

Script 2: receive_arr.sh

function process_arr() { 
    declare -a hash=("${!1}") 
    for animal in "${hash[@]}"; do 
     echo "Key: ${animal%%:*}" 
     echo "Value: ${animal#*:}" 
    done 
} 

Tài liệu tham khảo:
Passing arrays as parameters in bash

2

Nếu bạn không muốn để xử lý nhiều biến hoặc khóa chỉ là số nhận dạng biến không hợp lệ, mảng của bạn được đảm bảo có ít hơn 256 mục, bạn có thể lạm dụng các giá trị trả lại hàm. Giải pháp này không yêu cầu bất kỳ subshell nào vì giá trị có sẵn như là một biến, cũng không phải bất kỳ sự lặp lại nào để hiệu suất kêu la. Cũng rất dễ đọc, gần giống như phiên bản Bash 4.

Dưới đây là phiên bản cơ bản nhất:

hash_index() { 
    case $1 in 
     'foo') return 0;; 
     'bar') return 1;; 
     'baz') return 2;; 
    esac 
} 

hash_vals=("foo_val" 
      "bar_val" 
      "baz_val"); 

hash_index "foo" 
echo ${hash_vals[$?]} 

Thông tin chi tiết và các biến thể trong this answer

1

này hóa ra là ridiculously dễ dàng. Tôi đã phải chuyển đổi một kịch bản bash 4 sử dụng một loạt các mảng kết hợp để bash 3.Hai chức năng trợ giúp này đã thực hiện tất cả:

array_exp() { 
    exp=${@//[/__} 
    eval "${exp//]}" 
} 

array_clear() { 
    unset $(array_exp "echo \${!$1__*}") 
} 

Tôi rất ngạc nhiên rằng điều này thực sự hiệu quả, nhưng đó là vẻ đẹp của bash. Ví dụ:

((all[ping_lo] += counts[ping_lo])) 

trở thành

array_exp '((all[ping_lo] += counts[ping_lo]))' 

Hoặc tuyên bố in này:

printf "%3d" ${counts[ping_lo]} >> $return 

trở thành

array_exp 'printf "%3d" ${counts[ping_lo]}' >> $return 

Cú pháp duy nhất thay đổi là giải phóng. Đây:

counts=() 

trở thành

array_clear counts 

và bạn thiết lập. Bạn có thể dễ dàng yêu cầu array_exp nhận ra các biểu thức như "=()" và xử lý chúng bằng cách viết lại chúng dưới dạng các biểu thức array_clear, nhưng tôi thích sự đơn giản của hai hàm trên.

+0

Điều này có vẻ thú vị. Tôi đang băn khoăn không biết phải làm gì với các câu lệnh như: let 'map [$ i] ++', đặc biệt là các dấu nháy đơn. Suy nghĩ/ý kiến? – Ray

+0

Trong câu trả lời của bạn ở trên, câu lệnh eval trong mảng_exp() không thành công khi tôi chạy tập lệnh; nó phàn nàn về "sự thay thế xấu" – Ray

Các vấn đề liên quan