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 $1contrario 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 $1si se define lo contrario, se utiliza el nombre de archivo de la entrada estándar del propio proceso.
/proc/$$/fd/0y /dev/stdin? Me di cuenta de que este último parece ser más común y parece más sencillo.
-ra su readcomando, para que no coma \ caracteres accidentalmente ; use while IFS= read -r linepara preservar los espacios en blanco iniciales y finales.
/bin/sh: ¿está utilizando un shell diferente de basho 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.
<&0de 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
printflugar deechopara evitar imprimir líneas vacías cuando la línea consiste en una sola-e,-no-E. Sin embargo, existe una solución mediante el usoenv POSIXLY_CORRECT=1 echo "$line"que ejecuta su GNU externoechoque 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 -ra su readcomando, 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, -no -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 ready en printflugar 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
readse lee de la entrada estándar por defecto , por lo que no hay necesidad de < /dev/stdin.
La echosolución agrega nuevas líneas cada vez que IFSrompe 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).
ready 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". $IFSecho-n
\nagregado por echo: Perl $_ incluye la línea que termina \ndesde la línea leída, mientras que bash readno. (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 cates 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 whilebucle no son accesibles fuera de la tubería. La bashforma de evitarlo es la sustitución de procesos :
while read -r line
do
echo "$line"
done < <(cat "$@")
Esto deja el whileciclo 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 linees 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 execapropiadamente.
#!/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 whilebucle. Estoy usando la REPLYvariable estándar , y en este caso no es necesario reiniciar IFS. Si desea otro nombre, debe restablecerlo IFSasí (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 readcomando 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
readsin IFS=y -r, y al pobre $linesin sus citas saludables.
read -rnotació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 -rse 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.
-rdeberí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 $linevariable pobre que perdió sus comillas. Lo arreglé readmientras estaba en eso. No arreglé el echoporque 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 | txtx < file.cpptx file.cppCuando 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 dashDebian) 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 catese archivo, de lo contrario cat, entrada estándar. Luego, la salida de toda la ifdeclaració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
catse 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.