sirve - ¿Cómo hacer una función bash que pueda leer desde la entrada estándar?
pasar parametros shell script (5)
Tengo algunos scripts que funcionan con parámetros, funcionan bien, pero me gustaría que sean capaces de leer desde stdin, desde un conducto, por ejemplo, un ejemplo, supongamos que esto se llama leer:
#!/bin/bash
function read()
{
echo $*
}
read $*
Ahora esto funciona con read "foo" "bar"
, pero me gustaría usarlo como:
echo "foo" | read
¿Cómo logro esto?
Aquí hay ejemplos de implementación de la función sprintf
en bash que usa printf
y entrada estándar:
sprintf() { local stdin; read -d '''' -u 0 stdin; printf "$@" "$stdin"; }
Ejemplo de uso:
$ echo bar | sprintf "foo %s"
foo bar
Esto le daría una idea de cómo la función puede leer desde la entrada estándar.
Descubrí que esto se puede hacer en una línea usando test
y awk
...
test -p /dev/stdin && awk ''{print}'' /dev/stdin
La test -p
prueba la entrada en una tubería, que acepta la entrada a través de stdin. Solo si la entrada está presente, queremos ejecutar el awk
ya que de lo contrario se colgará indefinidamente esperando una entrada que nunca llegará.
Puse esto en una función para que sea fácil de usar ...
inputStdin () {
test -p /dev/stdin && awk ''{print}'' /dev/stdin && return 0
### accepts input if any but does not hang waiting for input
#
return 1
}
Uso...
_stdin="$(inputStdin)"
Otra función utiliza awk sin la prueba para esperar la entrada de la línea de comandos ...
inputCli () {
local _input=""
local _prompt="$1"
#
[[ "$_prompt" ]] && { printf "%s" "$_prompt" > /dev/tty; }
### no prompt at all if none supplied
#
_input="$(awk ''BEGIN {getline INPUT < "/dev/tty"; print INPUT}'')"
### accept input (used in place of ''read'')
### put in a BEGIN section so will only accept 1 line and exit on ENTER
### WAITS INDEFINITELY FOR INPUT
#
[[ "$_input" ]] && { printf "%s" "$_input"; return 0; }
#
return 1
}
Uso...
_userinput="$(inputCli "Prompt string: ")"
Tenga en cuenta que el > /dev/tty
en la primera impresiónf parece ser necesario para hacer que el prompt se imprima cuando se invoca la función en un Command Substituion $(...)
.
Este uso de awk
permite la eliminación del peculiar comando de read
para recopilar información desde el teclado o stdin.
Es un poco complicado escribir una función que puede leer la entrada estándar, pero funciona correctamente cuando no se proporciona una entrada estándar. Si simplemente intenta leer desde la entrada estándar, se bloqueará hasta que reciba alguno, al igual que si simplemente escribe cat
en el aviso.
En bash 4, puede solucionar esto utilizando la opción -t
para read
con un argumento de 0. Tiene éxito si hay alguna entrada disponible, pero no consume nada de ella; de lo contrario, falla.
Aquí hay una función simple que funciona como cat
si tiene algo de entrada estándar, y echo
contrario.
catecho () {
if read -t 0; then
cat
else
echo "$*"
fi
}
$ catecho command line arguments
command line arguments
$ echo "foo bar" | catecho
foo bar
Esto hace que la entrada estándar tenga prioridad sobre los argumentos de la línea de comandos, es decir, echo foo | catecho bar
echo foo | catecho bar
saldría foo
. Para hacer que los argumentos tengan prioridad sobre la entrada estándar ( echo foo | catecho bar
salidas echo foo | catecho bar
bar
), puede usar la función más simple
catecho () {
if [ $# -eq 0 ]; then
cat
else
echo "$*"
fi
}
(que también tiene la ventaja de trabajar con cualquier shell compatible con POSIX, no solo con ciertas versiones de bash
).
Para combinar un número de otras respuestas en lo que funcionó para mí (este ejemplo ideado convierte mayúsculas a minúsculas):
uppercase() {
local COMMAND=''tr [:lower:] [:upper:]''
if [ -t 0 ]; then
if [ $# -gt 0 ]; then
echo "$*" | ${COMMAND}
fi
else
cat - | ${COMMAND}
fi
}
Algunos ejemplos (el primero no tiene entrada, y por lo tanto no tiene salida):
:; uppercase
:; uppercase test
TEST
:; echo test | uppercase
TEST
:; uppercase <<< test
TEST
:; uppercase < <(echo test)
TEST
Paso a paso:
prueba si el descriptor de archivo 0 (
/dev/stdin
) fue abierto por un terminalif [ -t 0 ]; then
pruebas para argumentos de invocación de CLI
if [ $# -gt 0 ]; then
hacer eco de todos los argumentos CLI al comando
echo "$*" | ${COMMAND}
de lo contrario, si
stdin
se canaliza (es decir, no es entrada de terminal), la salidastdin
al comando (cat -
ycat
son la abreviatura decat /dev/stdin
)else cat - | ${COMMAND}
Puedes usar <<<
para obtener este comportamiento. read <<< echo "text"
debería hacerlo.
Prueba con readly
(prefiero no usar palabras reservadas):
function readly()
{
echo $*
echo "this was a test"
}
$ readly <<< echo "hello"
hello
this was a test
Con pipes, basado en esta respuesta a "Bash script, lea los valores de stdin pipe" :
$ echo "hello bye" | { read a; echo $a; echo "this was a test"; }
hello bye
this was a test