Существует ли эквивалентная команда `read -n` для оболочки Bourne?

Существует ли эквивалентная команда `read -n` для оболочки Bourne?

Есть ли способ заставить мой скрипт ждать, пока пользователь нажмет любую клавишу, а затем продолжить работу без нажатия Enter? Я хочу, чтобы это работало в оболочке Bourne ( sh), а не в Bash.

решение1

Предварительные заметки

В настоящее shвремяне обязательно устаревшая оболочка Bourne. В настоящее время shесть оболочка, которая поддерживаетпо меньшей мерефункции, требуемые POSIX (при условии правильной реализации). POSIX определяетshполезностьиЯзык команд оболочки.

Я предполагаю, что вы хотите, чтобы ваш код работал в оболочке POSIX и был переносимым в целом.


Проблемы

dd ibs=1 count=…это способ POSIX для чтения точного количества байтов. Похоже, это единственная переносимая утилита командной строки, которая может надежно выполнять эту работу. Но ddсчитывает байты, в то время как read -nв Bash считываетперсонажи.POSIX допускает многобайтовые символы в локалях, отличных отPOSIX. Даже если вы запустите весь скрипт с помощью LC_ALL=POSIX, терминал (эмулятор терминала) все равно может сгенерировать многобайтовую последовательность при нажатии какой-то одной клавиши. Такая последовательность может не быть многобайтовым символом; это может быть escape-последовательность (например дляF1, также смэтот ответ).

После того, как вы прочитаете только один байт, остальное останется и будет прочитано всем, кто попытается выполнить чтение с терминала позже (это может быть часть вашего скрипта или интерактивной оболочки, из которой вы запускаете скрипт).

Кроме того, если вы бежите в одиночку dd ibs=1 count=1, то вы, скорее всего, обнаружите, что ничего не происходит, ddпока вы не превысите{MAX_INPUT}или{MAX_CANON}, или нажмите EnterилиCtrl+D(а в случае , если вы хотите предоставить меньше байтов, вам может потребоваться countнажать +1CtrlD дважды).

Для решения этих проблем вам необходимонеканонический режим. Использоватьstty -icanon. В целом, начать с этого stty rawкажется хорошей идеей.


Код

Следующий пример является доказательством концепции. Поскольку он манипулирует настройками линии терминала и считывает данные с терминалатыне долженвставьте его в интерактивную оболочку, это не сработает. Вставьте его в файл и запустите файл.

#!/bin/sh

echo "Press any key to continue."

saveterm="$(stty -g)"                     # save terminal state
stty raw
stty -echo -icanon min 1 time 0           # prepare to read one byte
dd ibs=1 count=1 >/dev/null 2>/dev/null   # read one byte
stty -icanon min 0 time 0                 # prepare to read lefotvers
while read none; do :; done               # read leftovers
stty "$saveterm"                          # restore terminal state

Примечания

  • Скрипт не проверяет, является ли его stdin терминалом, но в общем случае это должно быть так ([ -t 0 ]).

  • В нормальной настройке клавиша-модификатор (например, Shift), нажатая отдельно, не отправляет никаких входных данных ни на что, считываемое с терминала; поэтому наш код не зарегистрирует такую ​​клавишу как «любую клавишу».

  • Прием «прочитать остатки» взят изэтот ответ.

  • Благодаря stty raw Ctrl+ Cили Ctrl+ Zможет быть "любой клавишей", а не посылать SIGINTили SIGSTOPсоответственно. Тем не менее, интерпретирующая скрипт оболочка может получить сигнал откуда-то еще, поэтому в общем случае может случиться, что она выйдет до того, как дойдет до stty "$saveterm"; поэтому может случиться, что она оставит терминал в состоянии, не подходящем для интерактивного использования. Вы можете захотеть перехватить соответствующие сигналы и восстановить исходное состояние терминала в любом случае.

  • Это правдапеременные следует заключать в двойные кавычкив общем. Здесь $savetermнамеренно не кавычки, так как stty -gгенерирует неуказанный вывод. Реализациям sttyразрешено генерировать вывод с пробелами и позже ожидать, что оболочка разделит его и передаст несколько аргументов. Указано ограничение, что вывод stty -gдолжен быть безопасным, если не заключен в кавычки, он не должен вызывать расширение слов в оболочке. Для переносимости $savetermлучше не использовать кавычки.

    Редактировать:это был дефект в спецификации POSIX. Приведенный выше код был исправлен.

  • Чтобы узнать, какой байт был прочитан, вам нужно сохранить вывод dd, либо в обычный файл ( >some_file), который вы изучите позже, либо в переменную ( variable="$(dd …)"). Но:

    • Терминал может генерировать нулевой байт (обычно с помощью Ctrl+ @), но большинство (все?) реализаций shне могут сохранять нулевой байт в переменной. Сохранение в файле допустимо, если только вы можете исследовать/манипулировать его содержимым, не считывая его в переменную.
    • ddиз приведенного выше кода вы получите только один байт, этого может быть недостаточно, чтобы определить символ или клавишу. С другой стороны, одного байта должно быть достаточно, чтобы отличить, например, Qот чего-либо еще, поэтому Press Q to quit or any other key to continue.или P - proceed; B - back; Q - quit; H - helpкажется возможным реализовать без анализа большего количества байтов.

А как насчет большего количества байтов?

Вышеуказанное должно быть хорошо для "нажмите любую клавишу". Настоящий эквивалент read -nдолжен считывать один байт за раз и декодировать последовательность, пока не получит желаемое количествоперсонажи. Я не собираюсь пытаться построить его здесь.

Связанный контент