Вопрос или проблема
Системная информация:
macOS Sierra 10.12.6
zsh 5.4.2 (x86_64-apple-darwin16.7.0)
GNU bash, версия 4.4.12(1)-release (x86_64-apple-darwin16.3.0)
Прокрутите к ПРИМЕРАМ внизу, если вы просто хотите ознакомиться с упрощенными примерами, которые я сделал.
ПРИМЕЧАНИЕ: Я не являюсь большим пользователем zsh
.
Я смотрел на комбинации клавиш fzf
для bash
и zsh
.
Обратите внимание, как в обоих случаях выполняется переменная команда $(__fzfcmd)
. __fzfcmd
по умолчанию выводит fzf
в stdout, и подстановка параметров просто выполняет команду (fzf
), которая является результатом этого вывода.
Одно из отличий между скриптами bash
и zsh
заключается в том, что скрипт bash
дополнительно перенаправляет вывод $(__fzfcmd)
, а zsh
просто захватывает его в массив. Могу предположить, что это связано с проблемой в zsh
, когда вы дополнительно перенаправляете вывод fzf
, в результате чего вы не можете вводить данные в fzf
, и процесс, перенаправленный через fzf
, не получает стандартного ввода. Ваш единственный выбор — использовать ^Z
или ^C
. ^C
по какой-то причине, похоже, ставит процесс в фоновый режим. Или, может быть, они просто хотели, чтобы это было в массиве, чтобы они могли выполнить zle vi-fetch-history
на нем. В версии bash
происходит некая магия в комбинации клавиш с "\e^": history-expand-line
Теперь fzf
не важен. Похоже, вам нужна просто программа, которая выводит данные в tty
, чтобы ее можно было вызвать с помощью подстановки параметров и вызвать эту проблему. Поэтому я покажу несколько более простых примеров.
Вот некоторые другие команды, которые выводят данные в tty
, и могут вызывать эту проблему в zsh
:
- vipe (запуск редактора посередине канала)
'vim -'
(заставляет vim читать стандартный ввод. аналогично vipe, но не выводит в stdout)
В приведенных ниже примерах замените каждое появление vipe
на vim -
, если не хотите производить отдельную установку. Просто помните, что vim -
не выведет содержимое редактора в stdout, как это делает vipe
.
ПРИМЕРЫ:
1) echo 1 | vipe | cat # работает и в bash, и в zsh
2) echo 1 | $(echo vipe) | cat # работает только в bash. проблема с zsh без вывода, пока я не нажму `^C`:
^C
zsh: завершено echo 1 |
zsh: приостановлено (tty output) $(echo vipe) |
zsh: прерывание cat
# похоже, что процесс находится в фоновом режиме. Я все еще могу видеть его в команде jobs
3) cat <(echo 1 | $(echo vipe)) # проблема в zsh и bash. Я полагаю, что это связано с тем,
# что файл не завершил запись, и cat
# блокирует tty-вывод vipe
# их выводы `^C` просто:
^C # ничего особенного, как и ожидалось
4) cat < <(echo 1 | $(echo vipe)) # работает и в bash, и в zsh
5) echo 1 | $(echo vipe) > >(cat) # работает и в bash, и в zsh
# Ниже отсутствует входной канал для vipe.
# Напишите что-нибудь, а затем отправьте EOF с помощью ^D
6) vipe | cat # работает для обоих
7) $(echo vipe) | cat # работает для обоих
Теперь меня в основном интересует, почему 2)
имеет проблему для zsh
, но не для bash
, и почему 4)
и 5)
решают проблему для zsh
.
Требования для возникновения этой проблемы в zsh
выглядят именно так, как я указал в заголовке:
- входной канал
- команда, выполняемая с помощью подстановки переменных/параметров, которая имеет
tty
вывод - выходной канал
ОБНОВЛЕНИЕ
Я добавил еще одно обходное решение, которое не вызывает этой проблемы в zsh
, 5)
. Оно похоже на 4)
, но вместо перенаправления stdout
непосредственно в stdin
, я перенаправляю его в файл, который перенаправляет в stdin
с использованием подстановки процессов.
Я believe, что ваша проблема сводится к неправильному кавычению ваших подстановок.
Цитата из zsh:14 Expansion
Команда, заключенная в круглые скобки, предшествующие знаку доллара, как
$(...)
, или заключенная в обратные кавычки, например ‘...
’, заменяется на
ее стандартный вывод, с удалением любых добавочных символов новой строки. Если
подстановка не заключена в двойные кавычки, вывод разбивается
на слова с использованием параметра IFS. Подстановка$(cat foo)
может
быть заменена на эквивалентную, но более быструю$(<foo)
. В любом случае, если
опция GLOB_SUBST установлена, вывод поддается генерации имен файлов.
Обратите внимание, что пример №2 в вашем вопросе приводит к бесконечному выводу NULL, из-за:
Если подстановка не заключена в двойные кавычки, вывод
разбивается на слова с использованием параметра IFS.
Другими словами, оболочка бесконечно ждет echo
, потому что стандартный разделитель — ПРОБЕЛ, и echo никогда не завершится. Смотрите TLDP: Внутренние переменные. Это оставляет зависший канал для команды cat
.
Как догадка, я полагаю, что 4 и 5 работают благодаря перенаправлению вывода.
Ответ или решение
Проблема с вводом в терминал в Zsh при использовании конвейера для stdin и stdout с командой, имеющей вывод TTY
Введение
В данной статье подробно рассмотрим проблему, связанной с терминалом Zsh, в контексте использования механизмов конвейерного ввода-вывода, особенно когда речь идет о командах с выводом на TTY. Мы обсудим примеры, причины возникновения проблемы и возможные решения.
Системная информация
Рассматриваемая ситуация возникла на следующем окружении:
- Операционная система: macOS Sierra 10.12.6
- Zsh: версия 5.4.2
- Bash: версия 4.4.12
Описание проблемы
При выполнении команд, которые используют подстановку параметров и включают в себя команды, выводящие информацию в TTY, возникают трудности с вводом данных через стандартный ввод, особенно в окружении Zsh.
Пример проблемы:
echo 1 | $(echo vipe) | cat # работает только в bash, zsh вызывает проблему
При запуске данной команды в Zsh процесс vipe
, который ожидает ввода, блокируется и не предоставляет возможность ввода, что приводит к зависанию ожидания (например, требуется использовать ^C для прерывания). В отличие от этого, в Bash команда выполняется корректно.
Причины возникновения проблемы
-
Подстановка параметров в Zsh:
При использовании$()
в Zsh вывод обрабатывается как список слов с использованием параметра IFS (Internal Field Separator). Если вывод команды пуст, это приводит к непредсказуемому поведению. Например, в случае$(echo vipe)
будет ожидаться ввод, посколькуvipe
не завершает выполнение. -
Блокировка TTY:
Если команда (например,vipe
) пытается получить доступ к TTY (терминальному интерфейсу), но используется в конвейере таким образом, что не получает соответствующего ввода, возникает блокировка. Zsh не может передать ввод стандартной команды наvipe
, так как она уже заблокирована, ожидая ввода. -
Отсутствие отклика:
Использование таких конструкций, как в примере 2 из вопроса, приводит к бесконечному ожиданию, так какecho
не завершает выполнение, что вызывает зависание конвейера.
Примеры и альтернативные решения
Давайте изучим несколько примеров, чтобы лучше понять, как избежать подобной ситуации.
-
Рабочий пример в Zsh:
echo 1 | vipe | cat
-
Работа через процессное подменение:
cat <(echo 1 | $(echo vipe)) # улучшает ситуацию, но может вести к блокировке
-
Избежание блокировок:
Использование5)
из описанных примеров:echo 1 | $(echo vipe) > >(cat)
Этот подход работает, поскольку вывод перенаправляется в файл, который затем читается.
Заключение
Проблемы, возникающие в Zsh при использовании конвейеров с командами, выводящими данные на TTY, являются результатом сочетания блокировок из-за неправильной обработки ввода и особенностей подстановки параметров.
Понимание работы разных оболочек, таких как Bash и Zsh, критически важно для предотвращения подобных проблем. Описанные выше решения помогут вам избежать блокировок ввода и оптимизировать выполнение ваших команд в Zsh, что существенно увеличивает эффективность работы с терминалом.