Можно ли перебирать кортежи в 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
для разделения итераций. то есть, если вы перебираете массив, например, 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
И количество элементов теперь без ограничений. Не ищу голосов; просто думаю вслух. ЗАДАНИЕ 1 , REF2
В случаях, когда мои определения кортежей более сложные, я предпочитаю иметь их в heredoc:
while IFS=", " read -ra arr; do
echo "${arr[0]} and ${arr[1]}"
done <<EOM
c, 3
e, 5
EOM
Это сочетает в себе цикл по строкам heredoc с разделением строк на некотором желаемом разделяющем символе .