¿Cómo pasar cada línea de un archivo de texto como argumento a un comando?


42

Estoy buscando escribir un script que tome un .txtnombre de archivo como argumento, lea el archivo línea por línea y pase cada línea a un comando. Por ejemplo, se ejecuta command --option "LINE 1", luego command --option "LINE 2", etc. La salida del comando se escribe en otro archivo. ¿Cómo hago para hacer eso? No se por donde empezar.

Respuestas:


26

Usar while readbucle:

: > another_file  ## Truncate file.

while IFS= read -r LINE; do
    command --option "$LINE" >> another_file
done < file

Otra es redirigir la salida por bloque:

while IFS= read -r LINE; do
    command --option "$LINE"
done < file > another_file

Lo último es abrir el archivo:

exec 4> another_file

while IFS= read -r LINE; do
    command --option "$LINE" >&4
    echo xyz  ## Another optional command that sends output to stdout.
done < file

Si uno de los comandos lee input, sería una buena idea usar otro fd para input para que los comandos no lo coman (aquí suponiendo ksh, zsho bashpara -u 3, usar <&3en su lugar de forma portátil):

while IFS= read -ru 3 LINE; do
    ...
done 3< file

Finalmente para aceptar argumentos, puedes hacer:

#!/bin/bash

FILE=$1
ANOTHER_FILE=$2

exec 4> "$ANOTHER_FILE"

while IFS= read -ru 3 LINE; do
    command --option "$LINE" >&4
done 3< "$FILE"

¿Cuál podría ejecutarse como:

bash script.sh file another_file

Idea extra. Con bash, use readarray:

readarray -t LINES < "$FILE"

for LINE in "${LINES[@]}"; do
    ...
done

Nota: IFS=puede omitirse si no le importa que se reduzcan los valores de línea de los espacios iniciales y finales.


28

Otra opción es xargs.

Con GNU xargs:

< file xargs -I{} -d'\n' command --option {} other args

{} es el marcador de posición para la línea de texto.

Otros xargsno tienen -d, pero algunos tienen -0para entrada delimitada por NUL. Con eso, puedes hacer:

< file tr '\n' '\0' | xargs -0 -I{} command --option {} other args

En los sistemas conformes con Unix ( -Ies opcional en POSIX y solo se requiere para los sistemas conformes con Unix), deberá preprocesar la entrada para citar las líneas en el formato esperado por xargs:

< file sed 's/"/"\\""/g;s/.*/"&"/' |
  xargs -E '' -I{} command --option {} other args

Sin embargo, tenga en cuenta que algunas xargsimplementaciones tienen un límite muy bajo en el tamaño máximo del argumento (255 en Solaris, por ejemplo, el mínimo permitido por la especificación Unix).


reducible a<file xargs -L 1 -I{} command --option {} other args
iruvar

@iruvar, no, eso procesaría citas y eliminaría algunos espacios en blanco.
Stéphane Chazelas

7

Manteniendo precisamente la pregunta:

#!/bin/bash

# xargs -n param sets how many lines from the input to send to the command

# Call command once per line
[[ -f $1 ]] && cat $1 | xargs -n1 command --option

# Call command with 2 lines as args, such as an openvpn password file
# [[ -f $1 ]] && cat $1 | xargs -n2 command --option

# Call command with all lines as args
# [[ -f $1 ]] && cat $1 | xargs command --option

1
funcionó a las
mil maravillas

3

La mejor respuesta que encontré es:

for i in `cat`; do "$cmd" "$i"; done < $file

EDITAR:

... cuatro años después ...

Después de varios votos negativos y algo más de experiencia, recomendaría ahora lo siguiente

xargs -l COMMAND < file

3
Esto invocará el comando una vez por cada palabra en el archivo. Además, siempre debe citar referencias a variables de shell (como en do "$cmd" "$i";) a menos que tenga una razón para no hacerlo; si el archivo contuviera una *palabra, el código se ejecutaría $cmd *, lo que, por supuesto, ejecutaría el comando con una lista de los archivos en el directorio actual.
G-Man dice 'reinstalar a Monica' el

1
@ G-Man, excepto en zsh, el `cat`ya se expandiría *(el no citado $iaún podría expandir algunos comodines (una segunda ronda) si la expansión de `cat`introduce algunos). En cualquier caso, ese enfoque es erróneo.
Stéphane Chazelas

1
+1. Esto funcionará bien, si cada línea contiene solo la entrada necesaria para el comando que se utiliza sin espacios.
Subin Sebastian

Esto actúa en cada palabra, ¿hay una variante de esto que actúa en cada línea?
thebunnyrules

La pregunta era procesar una lista de nombres de archivo línea por línea.
Steffen Roller

2
    sed "s/'/'\\\\''/g;s/.*/\$* '&'/" <<\FILE |\
    sh -s -- command echo --option
all of the{&}se li$n\es 'are safely shell
quoted and handed to command as its last argument
following --option, and, here, before that echo
FILE

SALIDA

--option all of the{&}se li$n\es 'are safely shell
--option quoted and handed to command as its last argument
--option following --option, and, here, before that echo

-2
ed file.txt
%g/^/s// /
2,$g/^/-,.j
1s/^/command/
wq
chmod 755 file.txt
./file.txt

Tomar todas las líneas de un archivo y pasarlas como argumentos a un solo comando, es decir,

command line1 line2 line3 ....

Si necesita que la --optionbandera preceda a cada línea, cambie el segundo comando a:

%g/^/s// --option /

1
¿Qué -1 para usar ed ... realmente?
user2217522

3
No te rechacé, pero la persona que sí lo hizo probablemente tenía estas razones: (1) Esto no hace lo que pide la pregunta. Esto invoca el comando una vez con todo el contenido del archivo en la línea de comando; en lugar de una vez por línea . (2) Esto no hace nada para manejar caracteres que son especiales para el shell (que podrían estar en el archivo); por ejemplo, ', ", <, >, ;, etc (3) Esto crea un archivo temporal innecesaria. (4) Cosas como esta generalmente se hacen con "documentos aquí". (5) Tus edcomandos son torpes; Los dos primeros comandos se pueden reducir a %s/^/ /y %j.
G-Man dice 'reinstalar a Monica' el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.