Перебирать кортежи в bash?


88

Можно ли перебирать кортежи в bash?

Например, было бы здорово, если бы работало следующее:

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

Есть ли обходной путь, который позволяет мне как-то перебирать кортежи?


4
Исходя из фона Python, это действительно очень полезный вопрос!
Джон Цзян

5
Глядя на это четыре года спустя, я задаюсь вопросом, нет ли лучшего способа сделать это. О, мой бог.
Giszmo

Почти 8 лет спустя я тоже задавался вопросом, нет ли лучшего способа сделать это. Но этот ответ 2018 года мне кажется довольно хорошим: stackoverflow.com/a/52228219/463994
MountainX

Ответы:


87
$ 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

7
Приятно - просто хочу отметить, что IFSследует сохранить и сбросить до исходного значения, если это выполняется в командной строке. Кроме того, новое значение IFSможет быть установлено один раз перед запуском цикла, а не на каждой итерации.
Egg, ровно

1
В случае, если любая из $ i начинается с дефиса, безопаснееset -- $i
Гленн Джекман

1
Вместо экономии IFS, только установить его для setкоманды: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Пожалуйста, отредактируйте свой ответ: если все читатели выберут только одно из перечисленных решений, нет смысла читать полную историю разработки. Спасибо за крутой фокус!
cfi

Если я объявлю tuples="a,1 b,2 c,3"и поставлю, IFS=','как в отредактированной версии, и вместо того, чтобы c,3 e,5использовать, $tuplesон вообще не будет хорошо печататься. Но вместо этого, если я помещаю IFS=','сразу после doключевого слова в цикл for, он хорошо работает как при использовании, $tuplesтак и при использовании литеральных значений. Просто подумал, что стоит сказать.
Simonlbc 02

@Simonlbc, потому что цикл for использует IFSдля разделения итераций. то есть, если вы перебираете массив, например, arr=("c,3" "e,5")и помещаете IFSперед циклом for, значение $iбудет просто cи e, оно будет разделено 3и 5поэтому setне будет правильно анализировать, потому $iчто не будет ничего для анализа. Это означает, что если значения для итерации не встроены, их IFSследует поместить внутрь цикла, а внешнее значение должно соответствовать намеченному разделителю для переменной, по которой выполняется итерация. В этом случае $tuplesдолжно быть просто IFS=значение по умолчанию, которое разбивается на пробелы.
untore

25

Я считаю, что это решение немного чище, чем другие, которые были представлены, h / t к этому руководству по стилю bash, чтобы проиллюстрировать, как можно использовать чтение для разделения строк по разделителю и присвоения их отдельным переменным.

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

17

Основываясь на ответе @ eduardo-ivanec без установки / сброса IFS, можно просто сделать:

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

Выход:

c and 3
e and 5

Этот подход кажется мне намного проще, чем принятый и получивший наибольшее количество голосов. Есть ли причина не делать это таким образом, в отличие от того, что предложил @Eduardo Ivanec?
spurra

@spurra, этот ответ был получен на 6 с половиной лет позже и основан на нем. Кредит там, где он должен.
Diego

1
@Diego Я знаю об этом. Это прямо написано в ответе. Я спрашивал, есть ли причина не использовать этот подход вместо принятого ответа.
spurra

2
@spurra, вы бы хотели использовать ответ Эдуардо, если разделитель по умолчанию (пробел, табуляция или новая строка ) по какой-то причине не работает для вас ( bash.cyberciti.biz/guide/$IFS )
Диего,

11

Используйте ассоциативный массив (также известный как словарь / 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

FYI, это не сработало для меня на Mac 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 в контейнере докеров.
Дэвид

похоже, что старые версии bash или те, которые не поддерживают эту функцию, работают на основе индекса? где ключ - это число, а не строка. tldp.org/LDP/abs/html/declareref.html , а вместо -Aнас -a.
Дэвид

Дэвид, похоже, так. Я думаю, вы можете попробовать индексы массивов, чтобы получить "ассоциативность". Вроде declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)и т.д. Еще не пробовал в терминале, но думаю, должно работать.
VasiliNovikov

7
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.


1
вы должны сделатьfor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox

1
Вау, это выигрывает награду «Самая уродливая группа произвольных персонажей, которых я видел сегодня». Кто-нибудь хочет объяснить, что именно делает эта мерзость? Я заблудился у знака
решетки

1
@koniiiik: Пояснение добавлено.
user unknown

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

Использование 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

1
Нет любви к этому? хорошо заставил меня преодолеть инерцию и установитьgnu parallel
StephenBoesch

2
brew install parallel
StephenBoesch

2

Использование 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

1
Да! Анубхава, милый гений!
Скотт

1
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


0

Но что, если кортеж больше, чем 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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.