¿Existe un comando equivalente `read -n` para Bourne Shell?

¿Existe un comando equivalente `read -n` para Bourne Shell?

¿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 shesno necesariamente el shell Bourne heredado. Hoy en día shes un caparazón que soportaal menoslas características requeridas por POSIX (suponiendo una implementación correcta). POSIX especifica elshutilidady 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 ddlee bytes, mientras que read -nen 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 ddhasta que excedas{MAX_INPUT}o{MAX_CANON}, o presione EnteroCtrl+D(y en caso de countser 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 rawparece 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 terminalno 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 enviar SIGINTo SIGSTOPrespectivamente. 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 a stty "$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í $savetermno se cita deliberadamente porque stty -ggenera una salida que no está especificada. Las implementaciones de sttypueden 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 salida stty -gdebe ser segura cuando no está entre comillas, no debe desencadenar la expansión de palabras en un shell. Para la portabilidad, $savetermes 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 shno 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.
    • dddel 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 que Press Q to quit or any other key to continue.parece P - proceed; B - back; Q - quit; H - helpposible implementarlo sin analizar más bytes.

¿Qué pasa con más bytes?

Lo anterior debería estar bien para "presionar cualquier tecla". Un equivalente real de read -ndebería leer un byte a la vez y decodificar la secuencia hasta obtener el número deseado decaracteres. No voy a intentar construirlo aquí.

información relacionada