¿Cómo escribir un script que acepte la entrada de un archivo o de stdin?

57

¿Cómo se puede escribir un script que acepte la entrada de un argumento de nombre de archivo o de stdin?

por ejemplo, podrías usarlo de lessesta manera. uno puede ejecutar less filenamey de manera equivalente cat filename | less.

¿Hay una manera fácil "fuera de la caja" para hacerlo? ¿o necesito reinventar la rueda y escribir un poco de lógica en el script?

gilad hoch
fuente
@PlasmaPower Siempre y cuando la pregunta sea sobre el tema en SU, no es necesario preguntar en un sitio de SE diferente. Muchos sitios de SE tienen superposición; por lo general, no necesitamos sugerir un sitio superpuesto a menos que la pregunta esté fuera de tema (en cuyo caso, vote para migrar) o sobre el tema, pero no obtenga mucha respuesta (en cuyo caso, el solicitante debe marcar como moderador) atención / migración, no cross-post).
Bob

Respuestas:

59

Si el argumento del archivo es el primer argumento de su script, pruebe que hay un argumento ( $1) y que es un archivo. Otra lectura de entrada de stdin -

Entonces su script podría contener algo como esto:

#!/bin/bash
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
cat $input

por ejemplo, puedes llamar al script como

./myscript.sh filename

o

who | ./myscript.sh

Editar Alguna explicación del guión:

[ $# -ge 1 -a -f "$1" ]- Si al menos un argumento de línea de comando ( $# -ge 1) Y (-a operador) el primer argumento es un archivo (-f prueba si "$ 1" es un archivo), entonces el resultado de la prueba es verdadero.

&&es el operador lógico AND del shell. Si la prueba es verdadera, entonces asigne input="$1"y cat $inputgenerará el archivo.

||es el operador lógico de shell OR. Si la prueba es falsa, se analizan los siguientes comandos ||. la entrada se asigna a "-". El comando se cat -lee desde el teclado.

Resumiendo, si se proporciona el argumento del script y es un archivo, entonces la entrada variable se asigna al nombre del archivo. Si no hay un argumento válido, cat lee desde el teclado.

sospechoso
fuente
¿ && input="$1" || input="-" Qué hace y por qué está fuera del testoperador?
cmo
He agregado una edición con alguna explicación que espero ayude.
sospechoso
¿Qué pasa si el script tiene múltiples argumentos ( $@)?
g33kz0r
12

readlee de la entrada estándar. Redirigirlo desde file ( ./script <someinput) o a través de pipe ( dosomething | ./script) no hará que funcione de manera diferente.

Todo lo que tiene que hacer es recorrer todas las líneas de entrada (y no difiere de iterar sobre las líneas del archivo).

(código de muestra, procesa solo una línea)

#!/bin/bash

read var
echo $var

Se hará eco de la primera línea de su entrada estándar (ya sea a través <o |).

Lukasz Daniluk
fuente
¡Gracias! Elijo la otra respuesta porque me quedaba mejor. Estaba envolviendo otro script, y no quería hacer un bucle hasta que se recibieran todas las entradas (podría ser una gran cantidad de entradas ... sería un desperdicio).
gilad hoch
4

No mencionas qué shell planeas usar, así que supongo que bash, aunque estas son cosas bastante estándar en todos los shells.

Argumentos de archivo

Se puede acceder a los argumentos a través de las variables $1- $n( $0devuelve el comando utilizado para ejecutar el programa). Digamos que tengo un script que solo catmuestra un número de archivos con un delimitador entre ellos:

#!/usr/bin/env bash
#
# Parameters:
#    1:   string delimiter between arguments 2-n
#    2-n: file(s) to cat out
for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2.
do
   cat $arg
   echo $1
done

En este caso, estamos pasando un nombre de archivo a cat. Sin embargo, si desea transformar los datos en el archivo (sin escribirlo y reescribirlo explícitamente), también puede almacenar el contenido del archivo en una variable:

file_contents=$(cat $filename)
[...do some stuff...]
echo $file_contents >> $new_filename

Leer de stdin

En cuanto a la lectura de stdin, la mayoría de los shells tienen un estándar bastante readintegrado, aunque hay diferencias en cómo se especifican las indicaciones (como mínimo).

La página de manual de Bash Builtins tiene una explicación bastante concisa read, pero prefiero la página de Bash Hackers .

Simplemente:

read var_name

Variables Múltiples

Para establecer múltiples variables, solo proporcione múltiples nombres de parámetros para read:

read var1 var2 var3

read luego colocará una palabra de stdin en cada variable, volcando todas las palabras restantes en la última variable.

λ read var1 var2 var3
thing1 thing2 thing3 thing4 thing5
λ echo $var1; echo $var2; echo $var3
thing1
thing2
thing3 thing4 thing5

Si se ingresan menos palabras que variables, las variables sobrantes estarán vacías (incluso si se configuraron previamente):

λ read var1 var2 var3
thing1 thing2
λ echo $var1; echo $var2; echo $var3
thing1
thing2
# Empty line

Avisos

Utilizo -pflag a menudo para un aviso:

read -p "Enter filename: " filename

Nota: ZSH y KSH (y quizás otros) usan una sintaxis diferente para las indicaciones:

read "filename?Enter filename: " # Everything following the '?' is the prompt

Valores predeterminados

Esto no es realmente un readtruco, pero lo uso mucho junto con read. Por ejemplo:

read -p "Y/[N]: " reply
reply=${reply:-N}

Básicamente, si la variable (respuesta) existe, devuélvala, pero si está vacía, devuelva el siguiente parámetro ("N").

Jacob Hayes
fuente
4

La forma más simple es redirigir stdin usted mismo:

if [ "$1" ] ; then exec < "$1" ; fi

O si prefiere la forma más concisa:

test "$1" && exec < "$1"

Ahora el resto de su script puede leer simplemente desde stdin. Por supuesto, puede hacerlo de manera similar con un análisis de opciones más avanzado en lugar de codificar la posición del nombre de archivo como "$1".

R ..
fuente
execintentaremos ejecutar el argumento como un comando que no es lo que queremos aquí.
Suzana
@Suzana_K: No cuando no tiene argumentos, como aquí. En ese caso, solo reemplaza a los descriptores de archivo para el shell en lugar de un proceso secundario.
R ..
Copié if [ "$1" ] ; then exec < "$1" ; fiun script de prueba y muestra un mensaje de error porque el comando es desconocido. Lo mismo con la forma concisa.
Suzana
1
@Suzana_K: ¿Qué shell estás usando? Si eso es cierto, no es una implementación funcional del comando POSIX sh / Bourne shell.
R ..
GNU bash 4.3.11 en Linux Mint Qiana
Suzana
3

usa (o encadena) algo más que ya se comporta de esta manera, y usa "$@"

Digamos que quiero escribir una herramienta que reemplace las ejecuciones de espacios en el texto con pestañas

tres la forma más obvia de hacer esto, pero solo acepta stdin, por lo que tenemos que encadenar cat:

$ cat entab1.sh
#!/bin/sh

cat "$@"|tr -s ' ' '\t'
$ cat entab1.sh|./entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ ./entab1.sh entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ 

para un ejemplo donde la herramienta que se usa ya se comporta de esta manera, podríamos reimplementar esto con sed:

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 
Aaron Davies
fuente
3

También puedes hacer:

#!/usr/bin/env bash

# Set variable input_file to either $1 or /dev/stdin, in case $1 is empty
# Note that this assumes that you are expecting the file name to operate on on $1
input_file="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Para más trucos de sustitución de parámetros en Bash, mira esto .

Daniel
fuente
1
¡Esto es fantástico! Estoy usando uglifyjs < /dev/stdiny funciona de maravilla!
fregante
0

También puede mantenerlo simple y usar este código


Cuando crea un archivo de script pass_it_on.sh con este código,

#!/bin/bash

cat

Tu puedes correr

cat SOMEFILE.txt | ./pass_it_on.sh

y todo el contenido de stdin simplemente se arrojará a la pantalla.


Alternativamente, use este código para mantener una copia de stdin en un archivo y luego arrojarlo a la pantalla.

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

y aquí hay otro ejemplo, quizás más legible, explicado aquí:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Que te diviertas.

RaamEE

RaamEE
fuente
0

La forma más simple y compatible con POSIX es:

file=${1--}

que es equivalente a ${1:--}.

Luego lea el archivo como de costumbre:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
kenorb
fuente