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

341

¿Cómo puedo hacer que xargs ejecute el comando exactamente una vez para cada línea de entrada dada? 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. xargs luego divide esta lista en sublistas y llama a rm una vez por cada sublista. Esto es más eficiente que esta versión funcionalmente equivalente:

find / path -type f -exec rm '{}' \;

Sé que find tiene la bandera "exec". Solo estoy citando un ejemplo ilustrativo de otro recurso.

Solo lectura
fuente
44
En el ejemplo que proporcione, find /path -type f -deletesería aún más eficiente :)
tzot
trate de no usar xargs ...
Naib
66
OP, sé que esta pregunta es muy antigua, pero aún aparece en Google y en mi humilde opinión, la respuesta aceptada es incorrecta. Vea mi respuesta más larga a continuación.
Tobia
Considere cambiar su aceptación a la respuesta de @ Tobia, que es mucho mejor. La respuesta aceptada no maneja espacios en los nombres y no permite múltiples argumentos para el comando xargs, que es una de las características principales de xargs.
Gris

Respuestas:

392

Lo siguiente solo funcionará si no tiene espacios en su entrada:

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

de la página del 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
fuente
13
Para mí, puede ser xargs -n 1como el que usted mostró "lista de argumentos demasiado larga".
Wernight
19
Si MAX-LINESse omite, el valor predeterminado es 1, por lo tanto xargs -les suficiente. Ver info xargs.
Thor
3
@Wernight: "-n1" no proporciona 1 invocación por línea de entrada. Tal vez su línea de entrada era demasiado larga. demo: echo "foo bar" | xargs -n1 echo. por lo tanto, si canaliza cosas como 'ls', no manejará bien los espacios.
gatoatigrado
8
Esto está mal. -L 1no responde la pregunta original, y lo -n 1hace solo en una de las posibles interpretaciones. Vea mi larga respuesta a continuación.
Tobia
2
@Tobia: Responde la pregunta original, que se refería específicamente a las líneas de entrada. Eso es exactamente lo que -L 1hace. Para mí, el OP parecía estar claramente tratando de evitar el comportamiento de fragmentación predeterminado, y dado que esto fue aceptado, supongo que tenía razón. Su respuesta aborda un caso de uso ligeramente diferente en el que también desea el comportamiento de fragmentación.
Draemon
207

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

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

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

GNU xargspuede o no tener extensiones útiles que le permitan eliminar tr, pero no están disponibles en OS X y otros sistemas UNIX.

Ahora para la larga explicación ...


Hay dos problemas a tener en cuenta al usar xargs:

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

Para probar el comportamiento de xargs, necesitamos una utilidad que muestre cuántas veces se está ejecutando y con cuántos argumentos. No sé si hay una utilidad estándar para hacer eso, pero podemos codificarla con bastante facilidad en bash:

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

Suponiendo que lo guarde como 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 leerlo varias veces) y debe leerse así (cambia en negrita):

¿Cómo puedo hacer que xargs ejecute el comando exactamente una vez para cada argumento de entrada dado? Su comportamiento predeterminado es dividir la entrada en argumentos y ejecutar el comando las menos 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 espacios en blanco y llama al comando las menos 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 los dos problemas):

¿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 del espacio en blanco .

entonces la respuesta es más sutil.

Uno podría pensar que -L 1podría ser de ayuda, pero resulta que no cambia el análisis de argumentos. Solo ejecuta el comando una vez para 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 espacios 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 una manera multiplataforma (excluyendo las extensiones GNU) es -0, que divide la entrada alrededor de bytes NUL.

Entonces, solo es 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 se ve bien, incluido el espacio en blanco al final.

Finalmente, si combina esta técnica con -n 1, obtendrá exactamente una ejecución de comando por línea de entrada, cualquiera que sea la entrada que tenga, 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" 
Tobia
fuente
Parece que esta es la mejor respuesta. sin embargo, todavía no entiendo cuál es la diferencia entre -L y -n ... ¿puedes explicar un poco más?
olala
55
@olala -Lejecuta el comando una vez por línea de entrada (pero un espacio al final de una línea lo une a la siguiente línea, y la línea aún se divide en argumentos según el espacio en blanco); while -nejecuta el comando una vez por argumento de entrada. Si cuenta el número de ->en los ejemplos de salida, ese es el número de veces que ./showse ejecuta el script .
Tobia el
¡Veo! no se dio cuenta de que un espacio al final de una línea lo une a la siguiente línea. ¡Gracias!
olala
44
GNU xargspuede o no tener extensiones útiles que le permitan eliminarlo.tr Tiene una extensión muy útil; from xargs --help- -d, --delimiter = CHARACTER Los elementos en la secuencia de entrada están separados por CHARACTER, no por espacios en blanco; desactiva el procesamiento de cotizaciones y barras invertidas y el procesamiento lógico de EOF
Piotr Dobrogost
Esta respuesta parece confundida con respecto -L. -Lno dice cuántas veces ejecutar el script por línea, dice cuántas líneas de datos de entrada consumir a la vez.
Moberg
22

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

Tratar:

find ruta de -type f -exec su comando {} \;

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

EDITAR:

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

De man xargs:

-L max-lines
Utilice como máximo líneas de entrada no en blanco de max- lines por línea de comando. Los espacios en blanco finales 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 archivo que terminan en blanco podrían causarle problemas si usa 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, mejor usa -print0y -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
tzot
fuente
17

¿Cómo puedo hacer que xargs ejecute el comando exactamente una vez para cada línea de entrada dada?

-L 1es la solución simple pero no funciona si alguno de los archivos contiene espacios en ellos. Esta es una función clave del -print0argumento find : separar los argumentos por el carácter '\ 0' en lugar de espacios en blanco. Aquí hay un ejemplo:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Una mejor solución es usar trpara convertir nuevas líneas en caracteres nulos ( \0), y luego usar el xargs -0argumento. Aquí hay un ejemplo:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Si luego necesita limitar el número de llamadas, puede usar el -n 1argumento para hacer una llamada al programa para cada entrada:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Esto también le permite filtrar la salida de find antes de convertir los saltos en nulos.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
gris
fuente
1
Hay un error de sintaxis en el segundo bloque de código tr '\ n' '\ 0 \ => tr' \ n '' \ 0 ', intenté solucionarlo pero "Las ediciones deben tener al menos 6 caracteres" (esto parece estúpido como git negándose a comprometerse porque mi cambio fue inferior a 6 caracteres)
htaccess
1
¿Qué significa esto: "Otro problema con el uso -Ltambién es que no permite múltiples argumentos para cada xargsllamada de comando"?
Moberg
He mejorado mi respuesta para eliminar esa información extraña @Moberg.
Gris
11

Otra alternativa ...

find /path -type f | while read ln; do echo "processing $ln"; done
Ricardo
fuente
9

Estas dos formas también funcionan, y funcionarán para otros comandos que no usan find!

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

ejemplo de uso:

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

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

Alex Riedler
fuente
Esto emite una llamada de utilidad para cada elemento que no es óptimo.
Gris
7
find path -type f | xargs -L1 command 

es todo lo que necesitas.


fuente
4

El siguiente comando encontrará todos los archivos (-tipo f) /pathy luego los copiará cpa la carpeta actual. Tenga en cuenta el uso de if -I %para especificar un carácter de marcador de posición en la cplínea de comando para que los argumentos se puedan colocar después del nombre del archivo.

find /path -type f -print0 | xargs -0 -I % cp % .

Probado con xargs (GNU findutils) 4.4.0


fuente
2

Puede limitar el número de líneas o argumentos (si hay espacios entre cada argumento) usando las banderas --max-lines o --max-args, respectivamente.

  -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.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.
Solo lectura
fuente
0

Parece que no tengo suficiente reputación para agregar un comentario a la respuesta anterior de Tobia, por lo que estoy agregando esta "respuesta" para ayudar a aquellos de nosotros que quieran experimentar de xargsla misma manera en las plataformas de Windows.

Aquí hay un archivo por lotes de Windows que hace lo mismo que el script "show" rápidamente codificado de Tobia:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.
CrashNeb
fuente
0

Las respuestas de @Draemon parecen estar bien con "-0" incluso con espacio en el archivo.

Estaba probando el comando xargs y descubrí que "-0" funciona perfectamente con "-L". incluso los espacios son tratados (si la entrada fue nula terminada). Lo siguiente es un ejemplo :

#touch "file with space"
#touch "file1"
#touch "file2"

Lo siguiente dividirá los valores nulos y ejecutará el comando en cada argumento de la lista:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

entonces -L1ejecutará el argumento en cada carácter terminado en nulo si se usa con "-0". Para ver la diferencia intente:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

incluso esto se ejecutará una vez:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

El comando se ejecutará una vez ya que "-L" ahora no se divide en byte nulo. debe proporcionar "-0" y "-L" para que funcione.

Mohammad Karmi
fuente
-3

En su ejemplo, el punto de canalizar la salida de find a xargs es que el comportamiento estándar de la opción -exec de find es ejecutar el comando una vez para cada archivo encontrado. Si está utilizando find, y desea su comportamiento estándar, entonces la respuesta es simple: no use xargs para empezar.

Sherm Pendley
fuente
En realidad, lo que puedo implicar de las ediciones del OP es que los datos de entrada no tienen nada que ver find, y es por eso que no prefieren la -execopción.
tzot
-3

ejecute ant task clean-all en cada build.xml en la carpeta actual o subcarpeta.

find . -name 'build.xml' -exec ant -f {} clean-all \;
sergiofbsilva
fuente
No todos lo han antinstalado.
Gris