Чи можна циклічно перебирати кортежі в bash?
Як приклад, було б чудово, якби працювало наступне:
for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done
Чи є спосіб вирішення проблеми, який якось дозволяє мені перебирати кортежі?
Чи можна циклічно перебирати кортежі в bash?
Як приклад, було б чудово, якби працювало наступне:
for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done
Чи є спосіб вирішення проблеми, який якось дозволяє мені перебирати кортежі?
Відповіді:
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5
Про це використання set
(від man builtins
):
Будь-які аргументи, що залишаються після обробки параметрів, обробляються як значення для позиційних параметрів і присвоюються, відповідно, $ 1, $ 2, ... $ n
IFS=","
Встановлює роздільник полів так що кожен $i
отримує сегментований в $1
і $2
правильно.
Через цей блог .
Редагувати: більш правильна версія, як запропонував @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
IFS
слід зберегти та повернути до початкового значення, якщо це запустити в командному рядку. Крім того, нове IFS
можна встановити один раз, перед запуском циклу, а не кожну ітерацію.
set -- $i
IFS
, тільки встановити його для set
команди: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done
. Будь ласка, відредагуйте свою відповідь: Якщо всі читачі виберуть лише одне з перерахованих рішень, немає сенсу читати повну історію розробок. Дякую за цей класний трюк!
tuples="a,1 b,2 c,3"
і ставлю, IFS=','
як у відредагованій версії, і замість c,3 e,5
використання $tuples
це взагалі погано друкується. Але замість цього, якщо я вклав IFS=','
одразу після do
ключового слова у цикл for, він добре працює, коли використовується $tuples
, а також літеральні значення. Просто думав, що варто сказати.
IFS
для розділення ітерацій. тобто якщо ви прокручуєте масив типу like arr=("c,3" "e,5")
і ставите IFS
перед циклом for, значення $i
буде справедливим c
і e
, воно розділиться 3
і 5
тому set
не буде аналізуватись правильно, тому що $i
не буде чого розбирати. Це означає, що якщо значення для ітерації не вбудовані, значення IFS
слід помістити всередину циклу, а зовнішнє значення повинно відповідати призначеному роздільнику для змінної, для якої потрібно виконати ітерацію. У випадках $tuples
це повинно бути просто, IFS=
що є типовим і розбивається на пробіли.
Я вважаю, що це рішення трохи чистіше, ніж інші, що були представлені, h / t до цього посібника для стилю bash, щоб проілюструвати, як читання можна використовувати для розділення рядків на роздільник та присвоєння їх окремим змінним.
for i in c,3 e,5; do
IFS=',' read item1 item2 <<< "${i}"
echo "${item1}" and "${item2}"
done
На основі відповіді, даної @ eduardo-ivanec, не встановлюючи / не скидаючи IFS
, можна просто зробити:
for i in "c 3" "e 5"
do
set -- $i
echo $1 and $2
done
Вихід:
c and 3
e and 5
Використовуйте асоціативний масив (також відомий як словник / 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
Працює для bash4.0 +.
Якщо вам потрібні трійки замість пар, ви можете використовувати більш загальний підхід:
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
GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0)
, який був встановлений за допомогою brew, тому YMMV.
GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)
з Ubuntu 14.04 в контейнері докера.
-A
ми маємо -a
.
declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)
т. Д. Ще не пробував у терміналі, але я думаю, що це має спрацювати.
c=('a' 'c')
n=(3 4 )
for i in $(seq 0 $((${#c[*]}-1)))
do
echo ${c[i]} ${n[i]}
done
Іноді може бути більш зручним.
Для пояснення ugly
частини, як зазначено в коментарях:
seq 0 2 створює послідовність чисел 0 1 2. $ (cmd) є підстановкою команди, тому для цього прикладу виводиться seq 0 2
, що є послідовністю чисел. Але що таке верхня межа $((${#c[*]}-1))
,?
$ ((Somthing)) - це арифметичне розширення, тому $ ((3 + 4)) дорівнює 7 і т. д. Наш вираз є ${#c[*]}-1
, отже, щось - 1. Досить просто, якщо ми знаємо, що ${#c[*]}
таке.
c - це масив, c [*] - це просто весь масив, $ {# c [*]} - це розмір масиву, який у нашому випадку дорівнює 2. Тепер відкочуємо все назад: 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
. Оскільки останній елемент масиву має індекс, який є довжиною масиву - 1.
for i in $(seq 0 $(($#c[*]}-1))); do [...]
Використання GNU Parallel:
parallel echo {1} and {2} ::: c e :::+ 3 5
Або:
parallel -N2 echo {1} and {2} ::: c 3 e 5
Або:
parallel --colsep , echo {1} and {2} ::: c,3 e,5
gnu parallel
brew install parallel
Використання printf
в процесі заміни:
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
Вище вимагає bash
. Якщо bash
він не використовується, використовуйте простий конвеєр:
printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done
do echo $key $value
done < file_discriptor
наприклад:
$ 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
Трохи більше залученого, але може бути корисним:
a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done
Але що, якщо кортеж більше k / v, який може вмістити асоціативний масив? Що робити, якщо це 3 або 4 елементи? Можна розширити цю концепцію:
###---------------------------------------------------
### 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
Тоді результат виглядав би приблизно так:
$ ./assoc-array-tinkering.sh
Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4
І кількість елементів тепер без обмежень. Не шукає голосів; просто думаючи вголос. REF1 , REF2
У тих випадках, коли мої назви кортежів є більш складними, я вважаю за краще мати їх у гередоці:
while IFS=", " read -ra arr; do
echo "${arr[0]} and ${arr[1]}"
done <<EOM
c, 3
e, 5
EOM
Це поєднує в собі зациклювання ліній гередоку з розбиттям ліній на певний бажаний розділовий символ .