Есть ли способ заставить мой скрипт ждать, пока пользователь нажмет любую клавишу, а затем продолжить работу без нажатия 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
нажать +1
CtrlD дважды).
Для решения этих проблем вам необходимонеканонический режим. Использовать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
кажется возможным реализовать без анализа большего количества байтов.
- Терминал может генерировать нулевой байт (обычно с помощью Ctrl+ @), но большинство (все?) реализаций
А как насчет большего количества байтов?
Вышеуказанное должно быть хорошо для "нажмите любую клавишу". Настоящий эквивалент read -n
должен считывать один байт за раз и декодировать последовательность, пока не получит желаемое количествоперсонажи. Я не собираюсь пытаться построить его здесь.