Как перебирать строку по одному слову за раз в zsh

Как изменить следующий код, чтобы при запуске в zsh он расширял $things и перебирал их по одному?

things="one two"

for one_thing in $things; do
    echo $one_thing
done

Я хочу, чтобы результат был:

one 
two

Но, как написано выше, он выводит:

one two

(Я ищу поведение, которое вы получаете при запуске приведенного выше кода в bash)


person Rob Bednark    schedule 18.04.2014    source источник
comment
Означает ли это, что zsh не разделяет слова $things, когда это список цикла for? Он входит в тело цикла только один раз? Ответ после прохождения сценария конфигурации — Да. Вычеркнуть zsh из списка используемых оболочек; это слишком похоже на оболочку POSIX. Я даже не могу начать думать о том, как это можно настроить для «нормальной» работы.   -  person Jonathan Leffler    schedule 18.04.2014
comment
Вам это может не понравиться, но такое поведение полностью совместимо с POSIX, и, насколько я знаю, это ожидаемое поведение для оболочки sh.   -  person SantiMar    schedule 31.07.2020


Ответы (4)


Чтобы увидеть поведение, совместимое с оболочкой Bourne, вам нужно установить параметр SH_WORD_SPLIT:

setopt shwordsplit      # this can be unset by saying: unsetopt shwordsplit
things="one two"

for one_thing in $things; do
    echo $one_thing
done

будет производить:

one
two

Однако рекомендуется использовать массив для разделения слов, например,

things=(one two)

for one_thing in $things; do
    echo $one_thing
done

Вы также можете обратиться к:

3.1. Почему $var where var="foo bar" не делает того, что я ожидал?

person devnull    schedule 18.04.2014
comment
Вместо того, чтобы устанавливать shwordsplit глобально, вы можете включить разбиение слов в стиле sh (используя, конечно, IFS) для одного параметра с ${=things}. - person chepner; 18.04.2014
comment
@chepner, как бы это выглядело? Я попробовал IFS=' '; for one_thing in ${=things}; do, но получил bad substitution ошибку - person Rob Bednark; 18.04.2014
comment
@RobBednark Почему бы вам не использовать массив? - person devnull; 18.04.2014
comment
Это должно быть просто for one_thing in ${=things}; do. Какую версию zsh вы используете? У меня работает в 4.3.10. Однако массив — лучшая идея, поскольку вам не нужно полагаться на использование разделителя строк, которого нет ни в одном из отдельных элементов. - person chepner; 18.04.2014
comment
@chepner, я использую zsh 5.0.2 (x86_64-apple-darwin13.0) - person Rob Bednark; 19.04.2014
comment
Кто-нибудь из зш-гуру вполне может объяснить остальным, зачем вообще существует эта практика. Я имею в виду, как по умолчанию не разбиваться на подстроки в цикле? - person gustafbstrom; 08.01.2019
comment
setopt shwordsplit не работает ни с zsh 5.3.1., ни с {=things}. Только IFS= сделал свое дело и ${(z)things}, как указано в другом ответе. - person pawamoy; 31.05.2019
comment
Совместимы ли массивы с POSIX? Я думал, что это не так. - person SantiMar; 31.07.2020

Вы можете использовать флаг расширения переменной z для разделения слов на переменную.

things="one two"

for one_thing in ${(z)things}; do
    echo $one_thing
done

Подробнее об этом и других флагах переменных читайте в разделе man zshexpn в разделе «Флаги расширения параметров».

person Kevin    schedule 19.04.2014

Другой способ, который также переносим между оболочками Bourne (sh, bash, zsh и т. д.):

things="one two"

for one_thing in $(echo $things); do
    echo $one_thing
done

Или, если вам не нужно, чтобы $things определялось как переменная:

for one_thing in one two; do
    echo $one_thing
done

Использование for x in y z заставит оболочку выполнить цикл по списку слов, y, z.

В первом примере используется подстановка команд для преобразования строки "one two" в список слов, one two (без кавычек ).

Второй пример - то же самое без echo.

Вот пример, который не работает, чтобы понять его лучше:

for one_thing in "one two"; do
    echo $one_thing
done

Обратите внимание на цитаты. Это просто напечатает

one two

потому что кавычки означают, что список состоит из одного элемента, one two.

person bgdnlp    schedule 20.02.2019

Вы можете предположить, что внутренний разделитель полей (IFS) в bash равен \x20 (пробел). Это делает следующую работу:

#IFS=$'\x20'
#things=(one two) #array
things="one two"  #string version

for thing in ${things[@]}
do
   echo $thing
done

Имея это в виду, вы можете реализовать это разными способами, просто манипулируя IFS; даже в многострочных строках.

person Atttacat    schedule 17.02.2017