Vòng qua các bộ giá trị trong bash?


88

Có thể lặp qua các bộ giá trị trong bash không?

Ví dụ, sẽ thật tuyệt nếu những điều sau đây hoạt động:

for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done

Có giải pháp nào cho phép tôi lặp lại các bộ giá trị không?


4
Đến từ nền tảng trăn, đây thực sự là một câu hỏi rất hữu ích!
John Jiang

5
Nhìn vào điều này bốn năm sau, tôi tự hỏi liệu vẫn còn không có cách nào tốt hơn để làm điều này. Chúa ơi.
Giszmo

Gần 8 năm sau, tôi cũng tự hỏi nếu vẫn không có cách nào tốt hơn để làm điều này. Nhưng câu trả lời năm 2018 này có vẻ khá ổn đối với tôi: stackoverflow.com/a/52228219/463994
MountainX

Câu trả lời:


87
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5

Về việc sử dụng set(từ man builtins) này:

Bất kỳ đối số nào còn lại sau khi xử lý tùy chọn được coi là giá trị cho các tham số vị trí và được gán, theo thứ tự, cho $ 1, $ 2, ... $ n

Các IFS=","bộ tách lĩnh vực như vậy mỗi $ibị tách ra thành $1$2chính xác.

Qua blog này .

Chỉnh sửa: phiên bản chính xác hơn, theo đề xuất của @SLACEDIAMOND:

$ OLDIFS=$IFS; IFS=','; for i in c,3 e,5; do set -- $i; echo $1 and $2; done; IFS=$OLDIFS
c and 3
e and 5

7
Tốt - chỉ muốn chỉ ra IFSnên được lưu và đặt lại về giá trị ban đầu nếu điều này được chạy trên dòng lệnh. Ngoài ra, cái mới IFScó thể được đặt một lần, trước khi vòng lặp chạy, thay vì mỗi lần lặp lại.
Eggxactly

1
Trong mọi trường hợp của $ i bắt đầu với một dấu nối, đó là an toàn hơn đểset -- $i
glenn jackman

1
Thay vì tiết kiệm IFS, chỉ đặt nó cho setlệnh: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Vui lòng chỉnh sửa câu trả lời của bạn: Nếu tất cả người đọc chỉ chọn một trong các giải pháp được liệt kê, thì không có ý nghĩa gì khi phải đọc toàn bộ lịch sử phát triển. Cảm ơn vì thủ thuật thú vị này!
cfi

Nếu tôi khai báo tuples="a,1 b,2 c,3"và đặt IFS=','như trong phiên bản đã chỉnh sửa, và thay vì c,3 e,5sử dụng $tuplesthì nó không in tốt chút nào. Nhưng thay vào đó, nếu tôi IFS=','chỉ đặt sau dotừ khóa trong vòng lặp for, nó sẽ hoạt động tốt khi sử dụng $tuplescũng như các giá trị nhỏ. Chỉ nghĩ rằng nó là giá trị nói.
Simonlbc

@Simonlbc đó là vì vòng lặp for dùng IFSđể chia nhỏ các lần lặp. tức là Nếu bạn lặp qua một mảng như arr=("c,3" "e,5")và đặt IFStrước vòng lặp for, giá trị của $isẽ chỉ là ce, nó sẽ tách ra 35do đó setsẽ không phân tích cú pháp chính xác vì $isẽ không có bất kỳ thứ gì để phân tích cú pháp. Điều này có nghĩa là nếu các giá trị để lặp không IFSđược đặt trong dòng, thì giá trị phải được đặt bên trong vòng lặp và giá trị bên ngoài phải tôn trọng dấu phân cách dự định cho biến để lặp lại. Trong trường hợp của $tuplesnó, nó chỉ nên IFS=là mặc định và phân chia theo khoảng trắng.
untore

25

Tôi tin rằng giải pháp này gọn gàng hơn một chút so với các giải pháp khác đã được gửi, hãy xem hướng dẫn kiểu bash này để minh họa cách đọc có thể được sử dụng để chia các chuỗi ở dấu phân cách và gán chúng cho các biến riêng lẻ.

for i in c,3 e,5; do 
    IFS=',' read item1 item2 <<< "${i}"
    echo "${item1}" and "${item2}"
done

17

Dựa trên câu trả lời được đưa ra bởi @ eduardo-ivanec mà không cần thiết lập / đặt lại IFS, người ta có thể chỉ cần làm:

for i in "c 3" "e 5"
do
    set -- $i
    echo $1 and $2
done

Đầu ra:

c and 3
e and 5

Đối với tôi, cách tiếp cận này có vẻ đơn giản hơn rất nhiều so với cách tiếp cận được chấp nhận và ủng hộ nhiều nhất. Có lý do gì để không làm theo cách này trái ngược với những gì @Eduardo Ivanec đã đề xuất?
spurra

@spurra câu trả lời này là 6 năm và ½ gần đây hơn, và dựa trên nó. Tín dụng đến hạn.
Diego

1
@Diego Tôi biết điều đó. Nó được viết rõ ràng trong câu trả lời. Tôi đã hỏi liệu có lý do gì để không sử dụng phương pháp này thay cho câu trả lời được chấp nhận hay không.
spurra

2
@spurra bạn muốn sử dụng câu trả lời của Eduardo nếu dấu phân tách mặc định (dấu cách, tab hoặc dòng mới) không hoạt động với bạn vì lý do nào đó ( bash.cyberciti.biz/guide/$IFS )
Diego

11

Sử dụng mảng kết hợp (còn được gọi là từ điển / hashMap):

declare -A pairs=(
  [c]=3
  [e]=5
)
for key in "${!pairs[@]}"; do
  value="${pairs[$key]}"
  echo "key is $key and value is $value"
done

Hoạt động cho bash4.0 +.


Nếu bạn cần bộ ba thay vì cặp, bạn có thể sử dụng cách tiếp cận chung hơn:

animals=(dog cat mouse)
declare -A sound=(
  [dog]=barks
  [cat]=purrs
  [mouse]=cheeps
)
declare -A size=(
  [dog]=big
  [cat]=medium
  [mouse]=small
)
for animal in "${animals[@]}"; do
  echo "$animal ${sound[$animal]} and it is ${size[$animal]}"
done

FYI, điều này không hoạt động với tôi trên máy Mac GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), được cài đặt thông qua brew, vì vậy YMMV.
David

Tuy nhiên, nó đã hoạt động trên GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)Ubuntu 14.04 trong bộ chứa docker.
David

Có vẻ như các phiên bản bash cũ hơn hoặc những phiên bản không hỗ trợ tính năng này hoạt động không hiệu quả dựa trên lập chỉ mục? trong đó khóa là một số thay vì chuỗi. tldp.org/LDP/abs/html/declareref.html , và thay vì -Achúng tôi có -a.
David

David, có vẻ như vậy. Tôi nghĩ bạn có thể thử các chỉ số mảng để có được "tính liên kết" sau đó. Như, declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)v.v. Tôi chưa thử nó trong thiết bị đầu cuối, nhưng tôi nghĩ nó sẽ hoạt động.
VasiliNovikov

7
c=('a' 'c')
n=(3    4 )

for i in $(seq 0 $((${#c[*]}-1)))
do
    echo ${c[i]} ${n[i]}
done

Đôi khi có thể tiện dụng hơn.

Để giải thích uglymột phần, như đã lưu ý trong các bình luận:

seq 0 2 tạo ra chuỗi số 0 1 2. $ (cmd) là lệnh thay thế, vì vậy trong ví dụ này, đầu ra của seq 0 2, là chuỗi số. Nhưng giới hạn trên là $((${#c[*]}-1))gì?

$ ((somthing)) là một khai triển số học, vì vậy $ ((3 + 4)) là 7, v.v. Biểu thức của chúng ta là ${#c[*]}-1, vì vậy một cái gì đó - 1. Khá đơn giản, nếu chúng ta biết nó ${#c[*]}là gì .

c là một mảng, c [*] chỉ là toàn bộ mảng, $ {# c [*]} là kích thước của mảng trong trường hợp của chúng ta là 2. Bây giờ chúng ta quay trở lại tất cả mọi thứ: for i in $(seq 0 $((${#c[*]}-1)))for i in $(seq 0 $((2-1)))for i in $(seq 0 1)for i in 0 1. Vì phần tử cuối cùng trong mảng có chỉ số là độ dài của Mảng - 1.


1
bạn nên làmfor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox

1
Chà, tác phẩm này đã giành được giải thưởng “Nhóm nhân vật tùy ý xấu xí nhất mà tôi đã thấy hôm nay”. Bất cứ ai quan tâm để giải thích chính xác những gì đáng ghê tởm này làm gì? Tôi bị lạc ở dấu thăng ...
koniiiik

1
@koniiiik: Đã thêm giải thích.
người dùng không xác định

6
$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5

3

Sử dụng song song GNU:

parallel echo {1} and {2} ::: c e :::+ 3 5

Hoặc là:

parallel -N2 echo {1} and {2} ::: c 3 e 5

Hoặc là:

parallel --colsep , echo {1} and {2} ::: c,3 e,5

1
Không có tình yêu cho điều này? cũng khiến tôi vượt qua quán tính và cài đặtgnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

Sử dụng printfthay thế quy trình:

while read -r k v; do
    echo "Key $k has value: $v"
done < <(printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3')

Key key1 has value: val1
Key key2 has value: val2
Key key3 has value: val3

Trên đây yêu cầu bash. Nếu bashkhông được sử dụng thì hãy sử dụng đường dẫn đơn giản:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done

1
Đúng! Anubhava, bạn là thiên tài ngọt ngào!
Scott

1
do echo $key $value
done < file_discriptor

ví dụ:

$ while read key value; do echo $key $value ;done <<EOF
> c 3
> e 5
> EOF
c 3
e 5

$ echo -e 'c 3\ne 5' > file

$ while read key value; do echo $key $value ;done <file
c 3
e 5

$ echo -e 'c,3\ne,5' > file

$ while IFS=, read key value; do echo $key $value ;done <file
c 3
e 5

0

Tham gia nhiều hơn một chút, nhưng có thể hữu ích:

a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done

0

Nhưng điều gì sẽ xảy ra nếu tuple lớn hơn k / v mà một mảng kết hợp có thể chứa? Nếu đó là 3 hoặc 4 phần tử thì sao? Người ta có thể mở rộng khái niệm này:

###---------------------------------------------------
### VARIABLES
###---------------------------------------------------
myVars=(
    'ya1,ya2,ya3,ya4'
    'ye1,ye2,ye3,ye4'
    'yo1,yo2,yo3,yo4'
    )


###---------------------------------------------------
### MAIN PROGRAM
###---------------------------------------------------
### Echo all elements in the array
###---
printf '\n\n%s\n' "Print all elements in the array..."
for dataRow in "${myVars[@]}"; do
    while IFS=',' read -r var1 var2 var3 var4; do
        printf '%s\n' "$var1 - $var2 - $var3 - $var4"
    done <<< "$dataRow"
done

Sau đó, đầu ra sẽ giống như sau:

$ ./assoc-array-tinkering.sh 

Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4

Và số lượng phần tử bây giờ là không có giới hạn. Không tìm kiếm phiếu bầu; chỉ suy nghĩ thành tiếng. REF1 , REF2


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.