Вопрос или проблема
Я наблюдаю различия между zsh и bash при использовании команды read на macOS.
В bash этот скрипт
echo "characters" | while IFS= read -d '' -n 1 a; do printf %s "$a-"; done
Выдает
c-h-a-r-a-c-t-e-r-s-
В то время как в zsh у меня нет вывода и ошибок. Возможно ли использовать read для итерации по символам?
Опции для команды read
значительно различаются между bash и zsh. В данном случае проблема в том, что -n
имеет совершенно разные значения в двух оболочках: в bash это указывает, сколько символов читать, тогда как в zsh это модифицирует опции -p
или -l
(которые относятся к функциям завершения, и здесь не имеют значения).
В zsh вы используете -k
, чтобы указать количество символов для чтения, но по умолчанию она читает с терминала, а не со стандартного ввода, поэтому вам нужно также добавить -u 0
, чтобы указать, что нужно читать со стандартного ввода.
Короче говоря: в zsh используйте read -n '' -k 1 -u 0 somevar
, чтобы прочитать один символ из стандартного ввода.
Кстати, между read
в bash и zsh (а также с другими оболочками) существует множество других различий. Стандарт POSIX только указывает на опцию -r
; все остальные – это нестандартные дополнения, и любое сходство между расширениями разных оболочек следует считать счастливой случайностью.
В интересах получения воспроизведения того, что интересовало оригинального автора поста, с минимальными изменениями, вот что-то, что делает в основном то же самое, вместе с выводом:
➜ ~ echo "characters" | while IFS= read -u 0 -k 1 a; do printf %s "$a-"; done
Результат:
c-h-a-r-a-c-t-e-r-s-
-%
Обратите внимание на дополнительные символы в строке. Я не исследовал это подробно, но, похоже, выводится перевод строки, который он находит в конце, за которым следует -
, а затем я подозреваю, что %
как-то связано с концом потока.
Я совершенно осведомлён, что оригинальный вопрос звучит: “Можно ли использовать read
для итерации по символам?” Вопрос также использует цикл while
.
Но если реальный акцент на итерации по символам, то есть два гораздо лучших способа сделать это (в zsh).
str="characters"
# Итерация по индексу
for i in {1..$#str}; do echo $str[i]; done
# Итерация по символу
for char in ${(s[])str}; do echo $char; done
Если вы просто хотите получить результат c-h-a-r-a-c-t-e-r-s
, просто сделайте
echo ${(j[-])${(s[])str}}
То есть: сначала разделите входную строку по пустому шаблону, затем снова соедините с помощью дефиса.
Ответ или решение
Различия в использовании команды read
в Bash и Zsh на macOS
При работе с командами оболочек Bash и Zsh пользователи часто сталкиваются с различиями в реализации и параметрах команд, особенно когда речь идет о чтении ввода с помощью команды read
. В данной статье мы подробно рассмотрим, как использовать read
для считывания символов один за другим в обоих шеллах и объясним, почему результат варьируется.
Пример работы в Bash
В Bash команда:
echo "characters" | while IFS= read -d '' -n 1 a; do printf %s "$a-"; done
производит следующий результат:
c-h-a-r-a-c-t-e-r-s-
В этом примере:
IFS=
устанавливает переменную внутреннего разделителя в пустую строку, что позволяетread
считывать каждый символ.-d ''
указывает на использование пустого разделителя, что позволяет читать символы до тех пор, пока поток не достигнет конца.-n 1
означает, что будет считан один символ за раз.
Проблемы с использованием в Zsh
В Zsh данная команда не работает так же, как в Bash. Когда вы запускаете аналогичный скрипт:
echo "characters" | while IFS= read -d '' -n 1 a; do printf %s "$a-"; done
вы не получаете никакого вывода.
Причиной тому являются различия в интерпретации параметров read
. В Zsh параметр -n
используется для изменения поведения опций -p
или -l
, которые относятся к функциям автодополнения, и не предназначен для указания количества считываемых символов.
Чтобы выполнить аналогичную задачу в Zsh, вам необходимо использовать следующее:
echo "characters" | while IFS= read -k 1 -u 0 a; do printf %s "$a-"; done
Здесь:
-k 1
указывает на считывание одного символа.-u 0
заставляетread
считывать из стандартного ввода (stdin), поскольку по умолчанию Zsh пытается читать из терминала.
Однако, как показано в вашем примере, это может также выдать неожиданные дополнительные символы в выводе, такие как -
и %
, что связано с окончанием потока.
Альтернативные способы итерации по символам в Zsh
Если ваша настоящая цель — перебор символов строки, существует более элегантный способ сделать это в Zsh:
- Итерация по индексу:
str="characters"
for i in {1..${#str}}; do echo $str[i]; done
- Итерация по символам:
for char in ${(s[])str}; do echo $char; done
- Формирование строки с разделителями:
Чтобы получить результат в таком виде, как c-h-a-r-a-c-t-e-r-s
, вы можете сделать следующее:
echo ${(j[-])${(s[])str}}
В этом случае:
${(s[])str}
разделяет строку на символы.${(j[-])...}
соединяет символы с разделителем-
.
Заключение
Несмотря на схожесть команд в Bash и Zsh, они имеют критические отличия, которые необходимо учитывать. Параметры, используемые в read
, и подходы к считыванию символов могут варьироваться, что обуславливает необходимость корректировки скриптов при переходе между этими оболочками. Вышеуказанные рекомендации помогут вам успешно использовать возможности каждой из оболочек, чтобы достигать желаемых результатов.
Не забывайте, что изучение специфики каждой оболочки может значительно улучшить ваши навыки и упростить повседневные задачи по автоматизации и обработке данных.