¿Hay alguna manera de hacer que mi script espere a que el usuario presione cualquier tecla y luego continúe sin que tenga que presionar Enter? Quiero que funcione en Bourne Shell ( sh
), no en Bash.
Respuesta1
Notas preliminares
Hoy en día sh
esno necesariamente el shell Bourne heredado. Hoy en día sh
es un caparazón que soportaal menoslas características requeridas por POSIX (suponiendo una implementación correcta). POSIX especifica elsh
utilidady elLenguaje de comandos de shell.
Supongo que desea que su código funcione en un shell POSIX y que sea portátil en general.
Asuntos
dd ibs=1 count=…
es una forma POSIX de leer un número exacto de bytes. Parece ser la única utilidad de línea de comandos portátil que puede hacer el trabajo de manera confiable. Pero dd
lee bytes, mientras que read -n
en Bash leecaracteres.POSIX permite caracteres de varios bytes en configuraciones regionales distintas aPOSIX
. Incluso si ejecuta el script completo con LC_ALL=POSIX
, el terminal (emulador de terminal) aún puede generar una secuencia de varios bytes con una sola pulsación de tecla. Dicha secuencia no puede ser un carácter de varios bytes; puede ser una secuencia de escape (por ejemplo paraF1, ver tambiénesta respuesta).
Después de leer solo un byte, el resto permanecerá y será leído por cualquier cosa que intente leer desde la terminal más adelante (puede ser parte de su secuencia de comandos o del shell interactivo desde donde ejecuta la secuencia de comandos).
Además, si corres lenguado dd ibs=1 count=1
, lo más probable es que descubras que nada llega dd
hasta que excedas{MAX_INPUT}
o{MAX_CANON}
, o presione EnteroCtrl+D(y en caso de count
ser más que 1
, si desea proporcionar menos bytes, es posible que deba presionar Ctrl+D dos veces).
Para abordar estos problemas es necesariomodo no canónico. Usarstty -icanon
. En general, empezar stty raw
parece una buena idea.
Código
El siguiente ejemplo es una prueba de concepto. Porque manipula la configuración de línea del terminal y lee desde el terminaltúno debepégalo en un shell interactivo, esto no funcionará. Pégalo en un archivo y ejecuta el archivo.
#!/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
Notas
El script no verifica si su entrada estándar es una terminal, pero en general debería (
[ -t 0 ]
).En una configuración sensata, una tecla modificadora (por ejemplo Shift), cuando se presiona sola, no envía ninguna entrada a lo que se lee desde el terminal; por lo tanto, nuestro código no registrará dicha clave como "cualquier clave".
El truco de "leer las sobras" está tomado deesta respuesta.
Gracias a
stty raw
Ctrl+ Co Ctrl+ Ztambién puede ser "cualquier clave", en lugar de enviarSIGINT
oSIGSTOP
respectivamente. Aún así, el shell que interpreta el script puede recibir una señal de otro lugar, por lo que, en general, puede suceder que salga antes de llegar astty "$saveterm"
; por lo que puede ocurrir que deje el terminal en un estado no apto para un uso interactivo. Es posible que desee capturar señales relevantes y restaurar el estado inicial del terminal de todos modos.Es ciertouno debe poner las variables entre comillas doblesen general. Aquí$saveterm
no se cita deliberadamente porquestty -g
genera una salida que no está especificada. Las implementaciones destty
pueden generar resultados con espacios y luego esperar que el shell los divida y pase múltiples argumentos. Lo que se especifica es una restricción cuya salidastty -g
debe ser segura cuando no está entre comillas, no debe desencadenar la expansión de palabras en un shell. Para la portabilidad,$saveterm
es mejor no cotizar.Editar:eso fue un defecto en la especificación POSIX. El código anterior ha sido corregido.
Para saber qué byte se leyó, debe guardar el resultado de
dd
, ya sea en un archivo normal (>some_file
) que examinará más adelante, o en una variable (variable="$(dd …)"
). Pero:- Un terminal puede generar un byte nulo (comúnmente con Ctrl+ @), pero la mayoría (¿todas?) de las implementaciones
sh
no pueden almacenar un byte nulo en una variable. Almacenar en un archivo está bien, siempre y cuando pueda examinar/manipular su contenido sin leerlo en una variable. dd
del código anterior le dará solo un byte, puede que no sea suficiente para indicar el carácter o la clave. OTOH, un solo byte debería ser suficiente para distinguirlo, por ejemplo, Qde cualquier otra cosa, por lo quePress Q to quit or any other key to continue.
pareceP - proceed; B - back; Q - quit; H - help
posible implementarlo sin analizar más bytes.
- Un terminal puede generar un byte nulo (comúnmente con Ctrl+ @), pero la mayoría (¿todas?) de las implementaciones
¿Qué pasa con más bytes?
Lo anterior debería estar bien para "presionar cualquier tecla". Un equivalente real de read -n
debería leer un byte a la vez y decodificar la secuencia hasta obtener el número deseado decaracteres. No voy a intentar construirlo aquí.