find + xargs: línea de argumento demasiado larga

21

Tengo una línea como la siguiente:

find /foo/bar -name '*.mp4' -print0 | xargs -i {} -0 mv -t /some/path {}

pero recibí el siguiente error:

xargs: argument line too long

Estoy confundido. ¿No se xargssupone que el uso de ayuda precisa con este problema?

Nota: Sé que puedo usar técnicamente -execen find, pero me gustaría entender por qué falla lo anterior, ya que entiendo que xargsse supone que debe saber cómo dividir la entrada en un tamaño manejable para el argumento que se ejecuta. ¿No es esto cierto?

Todo esto es con zsh.

Amelio Vazquez-Reina
fuente

Respuestas:

11

Bueno, por un lado, el -iinterruptor está en desuso:

-i[replace-str]
     This  option  is a synonym for -Ireplace-str if replace-str is specified. 
     If the replace-str argument is missing, the effect is the same as -I{}. 
     This option is deprecated; use -I instead.

Entonces, cuando cambié su comando a esto, funcionó:

$ find /foo/bar -name '*.mp4' -print0 | xargs -I{} -0 mv -t /some/path {}

Ejemplo

$ find . -print0 | xargs -I{} -0 echo {}
.
./.sshmenu
./The GIT version control system.html
./.vim_SO
./.vim_SO/README.txt
./.vim_SO/.git
./.vim_SO/.git/objects
./.vim_SO/.git/objects/pack
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.idx
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.pack
./.vim_SO/.git/objects/info
./.vim_SO/.git/refs
./.vim_SO/.git/refs/tags
...

Uso de -I{}

Este enfoque no debe usarse desde que se ejecuta esta construcción de comando:

$ find -print0 ... | xargs -I{} -0 ...

implícitamente activa estos interruptores a xargs, -xy -L 1. La -L 1configura xargspara que llame a los comandos que desea que ejecuten los archivos de una sola manera.

Entonces esto anula el propósito de usar xargsaquí ya que si le das 1000 archivos, ejecutará el mvcomando 1000 veces.

Entonces, ¿qué enfoque debo usar?

Puedes hacerlo usando xargs como este:

$ find /foot/bar/ -name '*.mp4' -print0 | xargs -0 mv -t /some/path

O simplemente haz que find lo haga todo:

$ find /foot/bar/ -name '*.mp4' -exec mv -t /some/path {} +
slm
fuente
¡Gracias! ¿Cuándo dijiste "This approach shouldn't be used"qué enfoque debería usarse en su lugar? ¿ "find /foot/bar/ -name '*.csv' -print0 | xargs -0 mv -t some_dir'"Sería una mejor solución? Si es así, ¿cómo no xargssaber en este caso en el que en el mvcomando para la alimentación de los argumentos que recibe de la tubería? (¿siempre los coloca al final?)
Amelio Vazquez-Reina
@ user815423426 - Hacerlo solo con el find ... -exec ...es una mejor manera o si quieres usar xargsel también find ... | xargs ... mv -t ...está bien. Sí, siempre los pone al final. Es por eso que ese método necesita el -t.
slm
5

La opción -itoma un argumento opcional. Como puso un espacio después -i, no hubo argumento para la -iopción y, por lo tanto, el siguiente -0no era una opción xargssino el segundo de 6 operandos {} -0 mv -t /some/path {}.

Con solo la opción -i, xargs esperaba una lista de nombres de archivos separados por una nueva línea. Como probablemente no había una nueva línea en la entrada, xargs recibió lo que parecía un nombre de archivo enorme (con bytes nulos incrustados, pero xargs no lo comprobó). Esta cadena única que contiene la salida completa de findera más larga que la longitud máxima de la línea de comando, de ahí el error "línea de comando demasiado larga".

Su comando habría funcionado con en -i{}lugar de -i {}. Alternativamente, podría haber usado -I {}: -Ies similar a -i, pero toma un argumento obligatorio, por lo que el siguiente argumento pasado a xargsse usa como argumento de la -Iopción. Luego, el argumento posterior es el -0que se interpreta como una opción, y así sucesivamente.

Sin embargo, no deberías usarlo -I {}en absoluto. Usar -Itiene tres efectos:

  • -Idesactiva el procesamiento de cotizaciones, que -0ya lo hace.
  • -Icambia la cadena para reemplazar, pero {}es el valor predeterminado.
  • -Ihace que el comando se ejecute por separado para cada registro de entrada, lo cual es inútil aquí ya que su comando ( mv -t) está específicamente diseñado para hacer frente a múltiples archivos por invocación.

O caer -Iy por -icompleto

find /foo/bar -name '*.mp4' -print0 | xargs -0 mv -t /some/path {}

o soltar xargs y usar -exec:

find /foo/bar -name '*.mp4' -exec mv -t /some/path {} +
Gilles 'SO- deja de ser malvado'
fuente
0

Intenta usar un bash para el bucle:

for FILE in *.mp4 ; do rm $FILE ; done

o si desea ver lo que está pasando:

for FILE in *.mp4 ; do echo Removing $FILE ; rm $FILE ; done
C. Shamis
fuente
0

Si está viendo esto mientras usa la concha de pescado .
Esto se relaciona con la forma en que el pescado expande la cadena de reemplazo{}

Si está utilizando pescado, debe escapar de la cadena de reemplazo \{\}

| xargs -I \{\} echo \{\}

o use una cadena de reemplazo diferente

| xargs -I ! echo !
nelaaro
fuente