zsh не может вводить данные в терминал, когда выполняется перенаправление stdin и stdout с переменной командой, которая имеет tty-вывод

Вопрос или проблема

Системная информация:

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 команда выполняется корректно.

Причины возникновения проблемы

  1. Подстановка параметров в Zsh:
    При использовании $() в Zsh вывод обрабатывается как список слов с использованием параметра IFS (Internal Field Separator). Если вывод команды пуст, это приводит к непредсказуемому поведению. Например, в случае $(echo vipe) будет ожидаться ввод, поскольку vipe не завершает выполнение.

  2. Блокировка TTY:
    Если команда (например, vipe) пытается получить доступ к TTY (терминальному интерфейсу), но используется в конвейере таким образом, что не получает соответствующего ввода, возникает блокировка. Zsh не может передать ввод стандартной команды на vipe, так как она уже заблокирована, ожидая ввода.

  3. Отсутствие отклика:
    Использование таких конструкций, как в примере 2 из вопроса, приводит к бесконечному ожиданию, так как echo не завершает выполнение, что вызывает зависание конвейера.

Примеры и альтернативные решения

Давайте изучим несколько примеров, чтобы лучше понять, как избежать подобной ситуации.

  1. Рабочий пример в Zsh:

    echo 1 | vipe | cat
  2. Работа через процессное подменение:

    cat <(echo 1 | $(echo vipe))  # улучшает ситуацию, но может вести к блокировке
  3. Избежание блокировок:
    Использование 5) из описанных примеров:

    echo 1 | $(echo vipe) > >(cat)

    Этот подход работает, поскольку вывод перенаправляется в файл, который затем читается.

Заключение

Проблемы, возникающие в Zsh при использовании конвейеров с командами, выводящими данные на TTY, являются результатом сочетания блокировок из-за неправильной обработки ввода и особенностей подстановки параметров.

Понимание работы разных оболочек, таких как Bash и Zsh, критически важно для предотвращения подобных проблем. Описанные выше решения помогут вам избежать блокировок ввода и оптимизировать выполнение ваших команд в Zsh, что существенно увеличивает эффективность работы с терминалом.

Оцените материал
Добавить комментарий

Капча загружается...