Cách đúng để sắp xếp một mảng liên quan trong bash hoặc zsh là gì?


8

Tôi đang tự hỏi làm thế nào tôi nên sắp xếp các mảng liên quan trong bash? Tôi đã thử hướng dẫn, nhưng dường như không có gì liên quan đến sắp xếp.

Giải pháp hiện tại là lặp lại mọi thứ và sử dụng chương trình bên ngoài, tức là key value | sort -k2

Điều đó có vẻ không hiệu quả với tôi.

Một ví dụ về mảng là:

A['192.168.2.2']=5
A['192.168.3.2']=1
A['192.168.1.1']=9

Và tôi sẽ tìm kiếm 2 địa chỉ IP được sử dụng hàng đầu, đó là 192.168.1.1 và 192.168.2.2, nghĩa là tôi cần sắp xếp mảng này theo giá trị của nó.


Có vẻ như những gì bạn đang cố gắng thực hiện là quá phức tạp bashđể dễ dàng thực hiện. Bạn đang cố làm gì vậy?
jw013

Và yêu cầu cách "đúng" chỉ là yêu cầu rắc rối. ;)
lynxlynxlynx


Là chuyển sang zsh một tùy chọn? Mặt khác, dựa vào các công cụ bên ngoài là phổ biến trong lập trình shell.
Gilles 'SO- ngừng trở nên xấu xa'

1
Nếu bạn nhận được dữ liệu này từ phân tích cú pháp và có sẵn gawk, tôi có xu hướng chỉ làm tất cả trong awk.
jordanm

Câu trả lời:


4

Zsh có một cách tích hợp để sắp xếp danh sách. Tuy nhiên, tôi không nghĩ có cách sắp xếp các giá trị trong khi vẫn giữ mối tương quan với các khóa bằng cờ mở rộng tham sốcờ phụ , điều đó có nghĩa là cần một vòng lặp rõ ràng. Giả sử rằng các giá trị của bạn không chứa ký tự null, bạn có thể tạo một mảng chứa các giá trị và khóa được nối với một ký tự null ở giữa và sắp xếp nó.

keys=("${(@k)A}")
values=("${(@v)A}")
combined=()
for ((i=1; i <= $#values; i++)) { combined[i]=($values[i]$'\0'$keys[i]); }
keys_sorted_by_decreasing_value=("${${(@On)combined}#*$'\0'}")
keys_of_the_top_two_values=("${(@)keys_sorted_by_decreasing_value[1,2]}")

EDIT bởi @sch: 4 dòng đầu tiên có thể được đơn giản hóa thành

combined=()
for k v ("${(@kv)A}") combined+=($k$'\0'$v)

Các biến keysvalueschứa các khóa và giá trị Atheo thứ tự tùy ý nhưng nhất quán. Bạn có thể viết keys=(${(k)A})nếu không có khóa trống và tương tự cho các giá trị. keys_sorted_by_decreasing_valuesắp xếp các khóa theo từ vựng, thêm ncờ để sắp xếp số ( 9trước 10) và loại bỏ Onếu bạn muốn sắp xếp theo thứ tự tăng dần (trong trường hợp đó có thể thu được hai giá trị trên cùng với chỉ mục [-2,-1]).

Ksh93 có cách chỉ sắp xếp các tham số vị trí, với set -s; Điều này cũng tồn tại trong zsh nhưng không có trong bash 4.2. Giả sử giá trị của bạn không chứa dòng mới hoặc ký tự điều khiển sắp xếp trước dòng mới:

keys=("${!A[@]}")
combined=()
for ((i=0; i <= ${#keys}; i++)); do combined[i]=(${A[${keys[$i]}]}$'\n'${keys[$i]}); done
set -A sorted -s "${combined[@]}"
top_combined=${sorted[${#sorted[@]}-1]}  # -2 for the next-to-largest, etc.
top_key=${top_combined#*$'\n'}

Đây là tất cả khá phức tạp, vì vậy bạn cũng có thể đi cho sắp xếp bên ngoài, dễ viết hơn nhiều. Giả sử rằng cả khóa và giá trị đều không chứa ký tự điều khiển, trong ksh hoặc bash:

IFS=$'\n'; set -f
keys_sorted_by_decreasing_value=($(
    for k in "${!A[@]}"; do printf '%s\t%s\n' "${A[$k]}" "$k"; done |
    sort | sed $'s/\t.*//'
  ))

3

Một shell là trước tất cả một công cụ để chạy các công cụ khác. Tôi nghe có vẻ như bạn đang theo một ngôn ngữ lập trình như perl, ruby, python ...

Đã nói rằng, đây là một số giải pháp có thể cho zsh.

Trong zsh, bạn có thể nhận danh sách các khóa của một mảng được liên kết ( ${(kOn)A}) hoặc của các giá trị ( ${(On)A}) nhưng không trực tiếp là danh sách các khóa từ danh sách giá trị được sắp xếp (AFAIK), nhưng bạn có thể thực hiện những việc như:

typeset -A A B
A=(
  192.168.2.2 5
  192.168.3.2 1
  192.168.1.1 9
  192.168.8.1 9
)

for v ("${(@nO)A}") B+=("${(@kv)A[(eR)$v]}")

Đó là, sắp xếp ( O) danh sách các giá trị ( $A) bằng số ( n) và fortừng value, thêm các cặp mắt k/ value khớp với giá trị $v( eđể khớp chính xác, Rđể lấy danh sách ngược dựa trên giá trị, không phải khóa) và thêm nó vào Bmảng kết hợp.

Sau đó, bạn sẽ nhận được danh sách được sắp xếp trong B:

$ printf '%s => %s\n' "${(@kv)B}"
192.168.8.1 => 9
192.168.1.1 => 9
192.168.2.2 => 5
192.168.3.2 => 1

Và bạn có thể chọn 2 phím đầu tiên với

$ print -rl -- ${${(k)B}[1,2]}
192.168.8.1
192.168.1.1

1

Cách tốt nhất để sắp xếp một mảng kết hợp bash bằng KEY là KHÔNG sắp xếp nó.

Thay vào đó, hãy lấy danh sách các KEYS, sắp xếp danh sách đó thành một biến và lặp qua danh sách. Ví dụ: Giả sử bạn có một mảng địa chỉ IP (khóa) và tên máy chủ (giá trị):

Thay thế: Tạo danh sách mới từ KEY, chuyển đổi thành dòng, sắp xếp nó, chuyển đổi trở lại danh sách và sử dụng nó để lặp qua mảng.

declare -A ADDR
ADDR[192.168.1.1]="host1"
ADDR[192.168.1.2]="host2"
etc...

KEYS=`echo ${!ADDR[@]} | tr ' ' '\012' | sort | tr '\012' ' '`
for KEY in $KEYS; do
  VAL=${ADDR[$KEY]}
  echo "KEY=[$KEY] VAL=[$VAL]"
done

0

"Mảng liên kết" thường có nghĩa là dữ liệu trong mảng có ý nghĩa trong thế giới thực, đó là trường hợp của bạn. Sắp xếp unix bên ngoài là lý tưởng cho nhiệm vụ này và ít lập trình viên C có thể thực hiện sắp xếp unix. Đặc biệt đối với dữ liệu lớn, bạn có thể điều chỉnh, cắt, nĩa, mang lại toàn bộ sức mạnh của unix và shell. Đây là lý do tại sao rất nhiều nền tảng shell và awk không bận tâm với một loại.

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.