Respuestas:
La siguiente solución se lee desde un archivo si la secuencia de comandos se llama con un nombre de archivo como primer parámetro de lo $1
contrario a partir de la entrada estándar.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
La sustitución se ${1:-...}
lleva a cabo $1
si se define lo contrario, se utiliza el nombre de archivo de la entrada estándar del propio proceso.
/proc/$$/fd/0
y /dev/stdin
? Me di cuenta de que este último parece ser más común y parece más sencillo.
-r
a su read
comando, para que no coma \
caracteres accidentalmente ; use while IFS= read -r line
para preservar los espacios en blanco iniciales y finales.
/bin/sh
: ¿está utilizando un shell diferente de bash
o sh
?
Quizás la solución más simple es redirigir stdin con un operador de redirección de fusión:
#!/bin/bash
less <&0
Stdin es el descriptor de archivo cero. Lo anterior envía la entrada canalizada a su script bash en el stdin de less.
<&0
de esta situación no tiene ningún beneficio : su ejemplo funcionará igual con o sin él; aparentemente, las herramientas que invoca desde un script bash de manera predeterminada ven el mismo stdin que el script en sí (a menos que el script lo consuma primero).
Aquí está la forma más simple:
#!/bin/sh
cat -
Uso:
$ echo test | sh my_script.sh
test
Para asignar stdin a la variable, puede usar: STDIN=$(cat -)
o simplemente STDIN=$(cat)
como operador no es necesario (según el comentario de @ mklement0 ).
Para analizar cada línea desde la entrada estándar , pruebe el siguiente script:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Para leer del archivo o stdin (si el argumento no está presente), puede extenderlo a:
#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Notas:
-
read -r
- No trate un carácter de barra invertida de ninguna manera especial. Considere que cada barra diagonal inversa sea parte de la línea de entrada.- Sin configuración
IFS
, por defecto las secuencias de Spacey Tabal principio y al final de las líneas se ignoran (recortan).- Use en
printf
lugar deecho
para evitar imprimir líneas vacías cuando la línea consiste en una sola-e
,-n
o-E
. Sin embargo, existe una solución mediante el usoenv POSIXLY_CORRECT=1 echo "$line"
que ejecuta su GNU externoecho
que lo admite. Ver: ¿Cómo hago eco de "-e"?
Ver: ¿Cómo leer stdin cuando no se pasan argumentos? en stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
a FILE=${1:--}
. (Quibble: mejor para evitar todas-mayúsculas cáscara variables en colisiones de nombres Evitar en caso de ambiente variables.)
${1:--}
es compatible con POSIX, por lo que debería funcionar en todos los shells similares a POSIX. Lo que no funcionará en todos estos shells es la sustitución de procesos ( <(...)
); funcionará en bash, ksh, zsh, pero no en el tablero, por ejemplo. Además, es mejor agregar -r
a su read
comando, para que no coma \
caracteres accidentalmente ; anteponer IFS=
para preservar los espacios en blanco iniciales y finales.
echo
: si una línea consiste en -e
, -n
o -E
, no se mostrará. Para solucionar este problema, debe utilizar printf
: printf '%s\n' "$line"
. No lo incluí en mi edición anterior ... con demasiada frecuencia mis ediciones se revierten cuando soluciono este error :(
.
--
es inútil si el primer argumento es'%s\n'
IFS=
con read
y en printf
lugar de echo
. :)
.
Creo que este es el camino directo:
$ cat reader.sh
#!/bin/bash
while read line; do
echo "reading: ${line}"
done < /dev/stdin
-
$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
echo "line ${i}"
done
-
$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
read
se lee de la entrada estándar por defecto , por lo que no hay necesidad de < /dev/stdin
.
La echo
solución agrega nuevas líneas cada vez que IFS
rompe el flujo de entrada. La respuesta de @ fgm se puede modificar un poco:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
's comportamiento: mientras read
no potencialmente dividido en varias fichas de los caracteres. contenido $IFS
, solo devuelve un token único si solo especifica un nombre de variable único (pero recorta y espacios en blanco iniciales y finales de forma predeterminada).
read
y agrega nuevas líneas sin la bandera. "La utilidad echo escribe los operandos especificados, separados por caracteres en blanco (` ') y seguidos por un carácter de nueva línea (`\ n'), en la salida estándar". $IFS
echo
-n
\n
agregado por echo
: Perl $_
incluye la línea que termina \n
desde la línea leída, mientras que bash read
no. (Sin embargo, como @gniourf_gniourf señala en otra parte, el enfoque más robusto es usar printf '%s\n'
en lugar de echo
).
El bucle Perl en la pregunta se lee de todos los argumentos de nombre de archivo en la línea de comando, o de la entrada estándar si no se especifican archivos. Las respuestas que veo parecen procesar un solo archivo o entrada estándar si no se especifica ningún archivo.
Aunque a menudo se ridiculiza con precisión como UUOC (uso inútil de cat
), hay momentos en que cat
es la mejor herramienta para el trabajo, y es discutible que esta sea una de ellas:
cat "$@" |
while read -r line
do
echo "$line"
done
El único inconveniente de esto es que crea una tubería que se ejecuta en un sub-shell, por lo que cosas como asignaciones de variables en el while
bucle no son accesibles fuera de la tubería. La bash
forma de evitarlo es la sustitución de procesos :
while read -r line
do
echo "$line"
done < <(cat "$@")
Esto deja el while
ciclo ejecutándose en el shell principal, por lo que las variables establecidas en el ciclo son accesibles fuera del ciclo.
>>EOF\n$(cat "$@")\nEOF
. Finalmente, una objeción: while IFS= read -r line
es una mejor aproximación de lo que while (<>)
hace en Perl (conserva los espacios en blanco iniciales y finales, aunque Perl también mantiene el final \n
).
El comportamiento de Perl, con el código dado en el OP, puede tomar ninguno o varios argumentos, y si un argumento es un guión simple, -
esto se entiende como stdin. Además, siempre es posible tener el nombre de archivo con $ARGV
. Ninguna de las respuestas dadas hasta ahora realmente imita el comportamiento de Perl en estos aspectos. Aquí hay una posibilidad pura de Bash. El truco es usarlo exec
apropiadamente.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Nombre de archivo disponible en $1
.
Si no se dan argumentos, los establecemos artificialmente -
como el primer parámetro posicional. Luego hacemos un bucle en los parámetros. Si un parámetro no es -
, redirigimos la entrada estándar del nombre del archivo con exec
. Si esta redirección tiene éxito, hacemos un bucle con un while
bucle. Estoy usando la REPLY
variable estándar , y en este caso no es necesario reiniciar IFS
. Si desea otro nombre, debe restablecerlo IFS
así (a menos que, por supuesto, no lo desee y sepa lo que está haciendo):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Con más precisión...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
y -r
al read
comando asegura que cada línea se lea sin modificaciones (incluidos los espacios en blanco iniciales y finales).
Por favor, intente el siguiente código:
while IFS= read -r line; do
echo "$line"
done < file
read
sin IFS=
y -r
, y al pobre $line
sin sus citas saludables.
read -r
notación. OMI, POSIX se equivocó; la opción debe habilitar el significado especial para las barras invertidas finales, no deshabilitarlo, de modo que los scripts existentes (anteriores a POSIX existieran) no se romperían porque -r
se omitió. Sin embargo, observo que era parte de IEEE 1003.2 1992, que era la primera versión del estándar POSIX de shell y utilidades, pero se marcó como una adición incluso entonces, por lo que esto es una queja sobre las oportunidades perdidas. Nunca he tenido problemas porque mi código no se usa -r
; Debo tener suerte. Ignórame sobre esto.
-r
debería ser estándar. Estoy de acuerdo en que es poco probable que ocurra en los casos en que no usarlo ocasione problemas. Sin embargo, el código roto es un código roto. Mi edición se activó por primera vez por esa $line
variable pobre que perdió sus comillas. Lo arreglé read
mientras estaba en eso. No arreglé el echo
porque ese es el tipo de edición que se revierte. :(
.
Code ${1:-/dev/stdin}
solo entenderá el primer argumento, entonces, ¿qué tal esto?
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
No encuentro ninguna de estas respuestas aceptables. En particular, la respuesta aceptada solo maneja el primer parámetro de línea de comando e ignora el resto. El programa Perl que está tratando de emular maneja todos los parámetros de la línea de comandos. Entonces la respuesta aceptada ni siquiera responde la pregunta. Otras respuestas usan extensiones bash, agregan comandos 'cat' innecesarios, solo funcionan para el caso simple de hacer eco de entrada a salida, o simplemente son innecesariamente complicados.
Sin embargo, tengo que darles algo de crédito porque me dieron algunas ideas. Aquí está la respuesta completa:
#!/bin/sh
if [ $# = 0 ]
then
DEFAULT_INPUT_FILE=/dev/stdin
else
DEFAULT_INPUT_FILE=
fi
# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
Combiné todas las respuestas anteriores y creé una función de shell que se adaptaría a mis necesidades. Esto es de un terminal cygwin de mis 2 máquinas con Windows10 donde tenía una carpeta compartida entre ellas. Necesito poder manejar lo siguiente:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Cuando se especifica un nombre de archivo específico, necesito usar el mismo nombre de archivo durante la copia. Cuando se ha canalizado el flujo de datos de entrada, entonces necesito generar un nombre de archivo temporal que tenga la hora minuto y segundo. La carpeta principal compartida tiene subcarpetas de los días de la semana. Esto es para fines organizativos.
He aquí, el último guión para mis necesidades:
tx ()
{
if [ $# -eq 0 ]; then
local TMP=/tmp/tx.$(date +'%H%M%S')
while IFS= read -r line; do
echo "$line"
done < /dev/stdin > $TMP
cp $TMP //$OTHER/stargate/$(date +'%a')/
rm -f $TMP
else
[ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
Si hay alguna forma en que pueda ver para optimizar aún más esto, me gustaría saber.
Lo siguiente funciona con estándar sh
(Probado con dash
Debian) y es bastante legible, pero eso es cuestión de gustos:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Detalles: si el primer parámetro no está vacío, entonces cat
ese archivo, de lo contrario cat
, entrada estándar. Luego, la salida de toda la if
declaración es procesada por commands_and_transformations
.
cat "${1:--}" | any_command
. Leer a las variables de shell y hacer eco de ellas puede funcionar para archivos pequeños, pero no escala tan bien.
[ -n "$1" ]
puede ser simplificado a [ "$1" ]
.
Qué tal si
for line in `cat`; do
something($line);
done
cat
se colocará en la línea de comando. La línea de comando tiene un tamaño máximo. Además, esto no se leerá línea por línea, sino palabra por palabra.