¿Cómo uso las líneas de un archivo como argumentos de un comando?

159

Digamos que tengo un archivo que foo.txtespecifica Nargumentos

arg1
arg2
...
argN

que necesito pasar al comando my_command

¿Cómo uso las líneas de un archivo como argumentos de un comando?

Yoo
fuente
10
"argumentos", plural o "argumento", ¿soltero? La respuesta aceptada es correcta solo en el caso de un solo argumento, que es sobre lo que pregunta el texto del cuerpo, pero no en el caso de múltiples argumentos, sobre el cual el tema parece preguntar.
Charles Duffy
Las 4 respuestas a continuación generalmente tienen resultados idénticos, pero tienen una semántica ligeramente diferente. Ahora recuerdo por qué dejé de escribir guiones de bash: P
Navin

Respuestas:

237

Si su shell es bash (entre otros), un atajo para $(cat afile)es $(< afile), por lo que escribiría:

mycommand "$(< file.txt)"

Documentado en la página de manual de bash en la sección 'Sustitución de comandos'.

Alternativamente, haga que su comando se lea desde stdin, entonces: mycommand < file.txt

Glenn Jackman
fuente
11
Para ser pedante, no es un atajo usar el <operador. Significa que el propio shell realizará la redirección en lugar de ejecutar elcat binario por sus propiedades de redirección.
Estilos
2
Es una configuración de kernel, por lo que deberá volver a compilar el kernel. O investiga el comando xargs
glenn jackman
3
@ykaner porque sin "$(…)", el contenido de file.txtse pasaría a la entrada estándar de mycommand, no como un argumento. "$(…)"significa ejecutar el comando y devolver la salida como una cadena ; aquí el "comando" solo lee el archivo pero podría ser más complejo.
törzsmókus
3
No me funciona a menos que pierda las comillas. Con las comillas, toma todo el archivo como 1 argumento. Sin comillas, interpreta cada línea como un argumento separado.
Pete
1
@LarsH tienes toda la razón. Esa es una forma menos confusa de decirlo, gracias.
Estilos
37

Como ya se mencionó, puede usar los backticks o $(cat filename).

Lo que no se mencionó, y creo que es importante tener en cuenta, es que debe recordar que el shell dividirá el contenido de ese archivo según el espacio en blanco, dando cada "palabra" que encuentre a su comando como argumento. Y si bien puede encerrar un argumento de línea de comandos entre comillas para que pueda contener espacios en blanco, secuencias de escape, etc., leer del archivo no hará lo mismo. Por ejemplo, si su archivo contiene:

a "b c" d

los argumentos que obtendrás son:

a
"b
c"
d

Si desea extraer cada línea como argumento, use la construcción while / read / do:

while read i ; do command_name $i ; done < filename
Será
fuente
Debería haber mencionado, supongo que estás usando bash. Me doy cuenta de que hay otros shells por ahí, pero casi todas las máquinas * nix en las que he trabajado corrieron bash o alguna equivalente. IIRC, esta sintaxis debería funcionar igual en ksh y zsh.
Will
1
La lectura debe realizarse a read -rmenos que desee expandir las secuencias de escape de barra diagonal inversa, y NUL es un delimitador más seguro que la nueva línea, particularmente si los argumentos que está pasando son nombres de archivos, que pueden contener nuevas líneas literales. Además, sin borrar IFS, obtiene espacios en blanco iniciales y finales borrados implícitamente i.
Charles Duffy
18
command `< file`

pasará el contenido del archivo al comando en stdin, pero eliminará las nuevas líneas, lo que significa que no puede iterar sobre cada línea individualmente. Para eso, podría escribir un script con un bucle 'for':

for line in `cat input_file`; do some_command "$line"; done

O (la variante multilínea):

for line in `cat input_file`
do
    some_command "$line"
done

O (variante multilínea con en $()lugar de ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Referencias

  1. Para la sintaxis de bucle: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
fuente
Además, poner < filebackticks significa que en realidad no realiza una redirección commanden absoluto.
Charles Duffy
3
(¿Las personas que votaron por esto en realidad lo probaron? command `< file` No funciona ni en bash ni en POSIX sh; zsh es un asunto diferente, pero ese no es el tema del que trata esta pregunta).
Charles Duffy
@CharlesDuffy ¿Puedes ser más específico sobre cómo no funciona?
mwfearnley
@CharlesDuffy Esto funciona en bash 3.2 y zsh 5.3. Esto no funcionará en sh, ash y dash, pero tampoco $ (<file).
Arnie97
1
@mwfearnley, feliz de proporcionar esa especificidad. El objetivo es iterar sobre líneas, ¿verdad? Poner Hello Worlden una línea. Lo verás primero some_command Hello, luego some_command World, no some_command "Hello World" o some_command Hello World.
Charles Duffy el
15

Lo haces usando backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
fuente
44
Esto no crea un argumento, porque no se cita, está sujeto a la división de cadenas, por lo que si emitió echo "Hello * Starry * World" > file.txten el primer paso, obtendría al menos cuatro argumentos separados pasados ​​al segundo comando, y probablemente más, ya que el *s se expandiría a los nombres de los archivos presentes en el directorio actual.
Charles Duffy
3
... y debido a que está ejecutando expansión global, no emite exactamente lo que hay en el archivo. Y debido a que está realizando una operación de sustitución de comandos, es extremadamente ineficiente: en realidad está fork()saliendo de una subshell con un FIFO adjunto a su stdout, luego invocando /bin/catcomo hijo de esa subshell, luego leyendo la salida a través del FIFO; compare con $(<file.txt), que lee el contenido del archivo en bash directamente sin subcapas ni FIFO involucrados.
Charles Duffy
11

Si desea hacer esto de una manera robusta que funcione para todos los argumentos posibles de la línea de comando (valores con espacios, valores con líneas nuevas, valores con comillas literales, valores no imprimibles, valores con caracteres globales, etc.), se pone un poco más interesante.


Para escribir en un archivo, dada una serie de argumentos:

printf '%s\0' "${arguments[@]}" >file

... reemplazar con "argument one", "argument two", etc., según corresponda.


Para leer ese archivo y usar su contenido (en bash, ksh93 u otro shell reciente con matrices):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Para leer ese archivo y usar su contenido (en un shell sin matrices; tenga en cuenta que esto sobrescribirá su lista de argumentos de la línea de comandos local, y por lo tanto, es mejor hacerlo dentro de una función, de modo que sobrescriba los argumentos de la función y no la lista global):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Tenga en cuenta que -d(permitir que se use un delimitador de fin de línea diferente) es una extensión que no es POSIX, y un shell sin matrices también puede no ser compatible. Si ese fuera el caso, es posible que deba usar un lenguaje que no sea shell para transformar el contenido delimitado por NUL en una evalforma segura:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"
Charles Duffy
fuente
6

Así es como paso el contenido de un archivo como argumento a un comando:

./foo --bar "$(cat ./bar.txt)"
chovy
fuente
1
Esto es, pero por ser sustancialmente menos eficiente, efectivamente equivalente a $(<bar.txt)(cuando se usa un shell, como bash, con la extensión adecuada). $(<foo)es un caso especial: a diferencia de la sustitución regular de comandos, como se usa en $(cat ...), no se bifurca en un subshell para operar, y por lo tanto evita una gran cantidad de sobrecarga.
Charles Duffy el
3

Si todo lo que necesita hacer es convertir el archivo arguments.txtcon contenido

arg1
arg2
argN

en my_command arg1 arg2 argNentonces puede simplemente utilizar xargs:

xargs -a arguments.txt my_command

Puede poner argumentos estáticos adicionales en la xargsllamada, como xargs -a arguments.txt my_command staticArgcuál llamarámy_command staticArg arg1 arg2 argN

Cobra_Fast
fuente
1

En mi bash shell, lo siguiente funcionó a las mil maravillas:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

donde input_file es

arg1
arg2
arg3

Como es evidente, esto le permite ejecutar múltiples comandos con cada línea desde input_file, un pequeño truco que aprendí aquí .

honkaboy
fuente
3
Su "pequeño truco" es peligroso desde una perspectiva de seguridad: si tiene un argumento que contiene $(somecommand), ejecutará ese comando en lugar de pasarlo como texto. Del mismo modo, >/etc/passwdse procesará como una redirección y sobrescritura /etc/passwd(si se ejecuta con los permisos adecuados), etc.
Charles Duffy,
2
En su lugar, es mucho más seguro hacer lo siguiente (en un sistema con extensiones GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _y también más eficiente, ya que pasa tantos argumentos a cada shell como sea posible en lugar de comenzar un shell por línea en su archivo de entrada.
Charles Duffy
Y, por supuesto, perder el uso inútil decat
tripleee
0

Después de editar la respuesta de @Wesley Rice un par de veces, decidí que mis cambios eran demasiado grandes para continuar cambiando su respuesta en lugar de escribir la mía. Entonces, ¡decidí que necesitaba escribir el mío!

Lea cada línea de un archivo y opere en él línea por línea de esta manera:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Esto viene directamente del autor Vivek Gite aquí: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . ¡Obtiene el crédito!

Sintaxis: Lea el archivo línea por línea en un shell Bash Unix y Linux:
1. La sintaxis es la siguiente para bash, ksh, zsh y todos los demás shells para leer un archivo línea por línea
2. while read -r line; do COMMAND; done < input.file
3. La -ropción pasada al comando de lectura evita que se interpreten los escapes de barra invertida.
4. Agregue la IFS=opción antes del comando de lectura para evitar que se recorte el espacio en blanco inicial / final -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


Y ahora para responder a esta pregunta ahora cerrada que también tuve: ¿Es posible 'agregar' una lista de archivos de un archivo?- Aquí está mi respuesta:

Tenga en cuenta que FILES_STAGEDes una variable que contiene la ruta absoluta a un archivo que contiene un montón de líneas donde cada línea es una ruta relativa a un archivo que me gustaría hacer git add. Este fragmento de código está a punto de formar parte del archivo "eRCaGuy_dotfiles / útiles_scripts / sync_git_repo_to_build_machine.sh" en este proyecto, para permitir la sincronización fácil de archivos en desarrollo desde una PC (por ejemplo: una computadora que codifico) a otra (por ejemplo: un computadora más potente sobre la que construyo): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Referencias

  1. Donde copié mi respuesta de: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Para la sintaxis de bucle: https://www.cyberciti.biz/faq/bash-for-loop/

Relacionado:

  1. Cómo leer el contenido del archivo línea por línea y hacerlo git adden él: ¿Es posible `git add` una lista de archivos de un archivo?
Gabriel Staples
fuente