Haga que xargs ejecute el comando una vez por cada línea de entrada

Resuelto readonly asked hace 15 años • 13 respuestas

¿Cómo puedo hacer que xargs ejecute el comando exactamente una vez por cada línea de entrada proporcionada? Su comportamiento predeterminado es dividir las líneas y ejecutar el comando una vez, pasando varias líneas a cada instancia.

De http://en.wikipedia.org/wiki/Xargs :

buscar /ruta -tipo f -print0 | xargs -0 rm

En este ejemplo, find alimenta la entrada de xargs con una larga lista de nombres de archivos. Luego, xargs divide esta lista en sublistas y llama a rm una vez para cada sublista. Esto es más eficiente que esta versión funcionalmente equivalente:

buscar /ruta -tipo f -exec rm '{}' \;

Sé que encontrar tiene el indicador "exec". Sólo estoy citando un ejemplo ilustrativo de otro recurso.

readonly avatar Oct 14 '08 05:10 readonly
Aceptado

Lo siguiente sólo funcionará si no tiene espacios en su entrada:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

desde la página de manual:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.
Draemon avatar Oct 13 '2008 22:10 Draemon

Me parece que todas las respuestas existentes en esta página son incorrectas, incluida la marcada como correcta. Esto se debe a que la pregunta está formulada de forma ambigua.

Resumen:   si desea ejecutar el comando "exactamente una vez por cada línea de entrada", pasando la línea completa (sin nueva línea) al comando como un único argumento, entonces esta es la mejor manera compatible con UNIX de hacerlo:

... | tr '\n' '\0' | xargs -0 -n1 ...

Si está utilizando GNU xargsy no necesita ser compatible con todos los demás UNIX (FreeBSD, Mac OS X, etc.), puede utilizar la opción específica de GNU -d:

... | xargs -d\\n -n1 ...

Ahora la explicación larga...


Hay dos cuestiones a tener en cuenta al utilizar xargs:

  1. ¿Cómo divide la entrada en "argumentos"? y
  2. cuántos argumentos pasar el comando secundario a la vez.

Para probar el comportamiento de xargs, necesitamos una utilidad que muestre cuántas veces se ejecuta y con cuántos argumentos. No sé si existe una utilidad estándar para hacer eso, pero podemos codificarlo fácilmente en bash:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Suponiendo que lo guarde showen su directorio actual y lo haga ejecutable, así es como funciona:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Ahora, si la pregunta original es realmente sobre el punto 2 anterior (como creo que es, después de leerla varias veces) y debe leerse así (cambios en negrita):

¿Cómo puedo hacer que xargs ejecute el comando exactamente una vez por cada argumento de entrada dado? Su comportamiento predeterminado es dividir la entrada en argumentos y ejecutar el comando la menor cantidad de veces posible , pasando múltiples argumentos a cada instancia.

entonces la respuesta es -n 1.

Comparemos el comportamiento predeterminado de xargs, que divide la entrada en torno a espacios en blanco y llama al comando la menor cantidad de veces posible:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

y su comportamiento con -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Si, por otro lado, la pregunta original era sobre el punto 1. División de entrada y debía leerse así (muchas personas que vienen aquí parecen pensar que ese es el caso, o están confundiendo las dos cuestiones):

¿Cómo puedo hacer que xargs ejecute el comando con exactamente un argumento para cada línea de entrada dada? Su comportamiento predeterminado es dividir las líneas alrededor de los espacios en blanco .

entonces la respuesta es más sutil.

Uno pensaría que eso -L 1podría ser de ayuda, pero resulta que no cambia el análisis de argumentos. Solo ejecuta el comando una vez por cada línea de entrada, con tantos argumentos como había en esa línea de entrada:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

No solo eso, sino que si una línea termina con un espacio en blanco, se agrega a la siguiente:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Claramente, -Lno se trata de cambiar la forma en que xargs divide la entrada en argumentos.

El único argumento que lo hace de forma multiplataforma (excluyendo las extensiones GNU) es -0, que divide la entrada en bytes NUL.

Entonces, es sólo cuestión de traducir nuevas líneas a NUL con la ayuda de tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Ahora el análisis del argumento parece correcto, incluido el espacio en blanco final.

Finalmente, si combinas esta técnica con -n 1, obtienes exactamente una ejecución de comando por línea de entrada, cualquiera que sea la entrada que tengas, lo que puede ser otra forma de ver la pregunta original (posiblemente la más intuitiva, dado el título):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 

Como se mencionó anteriormente, si está utilizando GNU, xargspuede reemplazar trcon la opción específica de GNU -d:

$ echo $'one \ntwo\nthree and four' | xargs -d\\n -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 
Tobia avatar Mar 02 '2015 10:03 Tobia

Si desea ejecutar el comando para cada línea (es decir, resultado) proveniente de find, ¿para qué lo necesita xargs?

Intentar:

find ruta -type f -exec tu-comando {} \;

donde el literal {}se sustituye por el nombre del archivo y el literal \;es necesario para findsaber que el comando personalizado termina allí.

EDITAR:

(después de la edición de su pregunta aclarando que conoce -exec)

De man xargs:

-L max-lines
Utilice como máximo líneas de entrada que no estén en blanco por línea de comando. Los espacios en blanco al final hacen que una línea de entrada continúe lógicamente en la siguiente línea de entrada. Implica -x.

Tenga en cuenta que los nombres de archivos que terminan en espacios en blanco le causarían problemas si utiliza xargs:

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Entonces, si no te importa la -execopción, será mejor que uses -print0y -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
tzot avatar Oct 13 '2008 22:10 tzot

¡Estas dos formas también funcionan y funcionarán para otros comandos que no utilizan buscar!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

caso de uso de ejemplo:

find . -name "*.pyc" | xargs -i rm '{}'

eliminará todos los archivos pyc en este directorio incluso si los archivos pyc contienen espacios.

Alex Riedler avatar Aug 15 '2014 00:08 Alex Riedler