Hacer que xargs maneje nombres de archivos que contienen espacios

252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

Mi comando falla porque el archivo "Lemon Tree.mp3" contiene espacios y xargs piensa que son dos archivos. ¿Puedo hacer que find + xargs funcione con nombres de archivo como este?

showkey
fuente
En lugar de ls |grep mp3 |sed -n "7p"solo puedes usar echo "Lemon Tree.mp3".
Micha Wiedenmann
Esta pregunta también es respondida por stackoverflow.com/a/33528111/94687
imz - Ivan Zakharyaschev

Respuestas:

256

El xargscomando toma caracteres de espacio en blanco (tabulaciones, espacios, nuevas líneas) como delimitadores. Puede reducirlo solo para los nuevos caracteres de línea ('\ n') con una -dopción como esta:

ls *.mp3 | xargs -d '\n' mplayer

Funciona solo con GNU xargs. Para sistemas BSD, use la -0opción como esta:

ls *.mp3 | xargs -0 mplayer

Este método es más simple y también funciona con los xargs de GNU.

Rayo
fuente
66
¡La mejor respuesta para uso general! Esto funciona incluso si su comando anterior no es "buscar"
nexayq
28
Desafortunadamente, esta opción no está disponible en OS X.
Thomas Tempelmann
25
@Thomas Para OS X, la bandera es -E, por ejemplo:xargs -E '\n'
30
En OS X, -E '\ n' no tuvo ningún efecto para mí, ni lo esperaría, ya que modificó el eofstr y no el separador de registros. Sin embargo, pude utilizar el indicador -0 como solución, incluso si el comando anterior no es 'buscar', simulando el efecto del indicador find -print0 en mi entrada, por ejemplo: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker
10
Para OS X, puede "BREW instalar findutils", que le da el comando "gxargs" que lo hace tener la opción -d.
Tom De Leu
213

La utilidad xargs lee cadenas delimitadas de espacio, tabulación, nueva línea y fin de archivo a partir de la entrada estándar y ejecuta la utilidad con las cadenas como argumentos.

Desea evitar usar el espacio como delimitador. Esto se puede hacer cambiando el delimitador para xargs. De acuerdo con el manual:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

Como:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

Para responder a la pregunta sobre la reproducción del séptimo mp3; es más simple correr

 mplayer "$(ls *.mp3 | sed -n 7p)"
Jens
fuente
10
Esto está usando GNU findy GNU xargs; no todas las versiones de esos programas son compatibles con esas opciones (aunque hay un caso que deberían hacer).
Jonathan Leffler
1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX lamentablemente teme a los caracteres NUL en los archivos de texto y aún no ha recibido suficiente terapia :-) Mi consejo, de hecho, recurre a opciones no portátiles.
Jens,
66
Y Mac OS X (un derivado de BSD) tiene findcon -print0y xargscon -0. Sin embargo, AFAIK, HP-UX, AIX y Solaris no lo hacen (pero debo corregirlo: HP-UX 11i no; Solaris 10 no; AIX 5.x no; pero no son versiones actuales ) No sería difícil cambiar sed, por ejemplo, usar 'líneas' que terminan en '\0'lugar de '\n', y el POSIX 2008 getdelim()facilitaría la administración.
Jonathan Leffler
2
Truco +1 + 1 para usar con rutas de archivo que contienen archivos de lista: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; imprimir' | xargs -0 zip $ zip_package
Yordan Georgiev
2
Buena idea para reemplazar las nuevas líneas con NUL. Tuve que hacer esto en un sistema incrustado que no tenía GNU find ni GNU xargs ni perl, pero el comando tr se puede aprovechar para hacer lo mismo: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson
28

Tratar

find . -name \*.mp3 -print0 | xargs -0 mplayer

en vez de

ls | grep mp3 
Scott C Wilson
fuente
16

xargs en MacOS no tiene la opción -d, por lo que esta solución usa -0 en su lugar.

Obtenga ls para generar un archivo por línea, luego traduzca las nuevas líneas en nulos y dígale a xargs que use nulos como delimitador:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer

Graeme Pyle
fuente
8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

Esto ayudó en mi caso a eliminar diferentes archivos con espacios. Debería funcionar también con mplayer. El truco necesario son las citas. (Probado en Linux Xubuntu 14.04.)

Bendel
fuente
7

La respuesta de Dick.Guertin [1] sugirió que uno podría escapar de los espacios en un nombre de archivo es una alternativa valiosa a otras soluciones sugeridas aquí (como usar un carácter nulo como separador en lugar de espacios en blanco). Pero podría ser más simple: realmente no necesitas un personaje único. Puede simplemente haber agregado los espacios escapados directamente:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

Además, el grep solo es necesario si solo desea archivos con espacios en los nombres. Más genéricamente (p. Ej., Al procesar un lote de archivos, algunos de los cuales tienen espacios, otros no), simplemente omita el grep:

ls | sed 's| |\\ |g' | xargs ...

Entonces, por supuesto, el nombre del archivo puede tener otros espacios en blanco que no sean espacios en blanco (por ejemplo, una pestaña):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

Eso supone que tiene un sed que admite -r (expresiones regulares extendidas) como GNU sed o versiones recientes de bsd sed (por ejemplo, FreeBSD que originalmente deletreaba la opción "-E" antes de FreeBSD 8 y admite tanto -r como -E para compatibilidad a través de FreeBSD 11 al menos). De lo contrario, puede usar una expresión de corchete de clase de caracteres regex básica e ingresar manualmente el espacio y los caracteres de tabulación en los []delimitadores.

[1] Esto quizás sea más apropiado como comentario o edición para esa respuesta, pero por el momento no tengo suficiente reputación para comentar y solo puedo sugerir ediciones. Dado que las últimas formas anteriores (sin el grep) alteran el comportamiento de la respuesta original de Dick.Guertin, una edición directa tal vez no sea apropiada de todos modos.

Juan
fuente
1
chicos locos de Unix que ejecutan scripts que nombran archivos sin considerar su salida, ese es quién
andrew lorien
4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

Tenga en cuenta que en el comando anterior, xargsllamará de mplayernuevo para cada archivo. Esto puede ser indeseable para mplayer, pero puede estar bien para otros objetivos.

Acumenus
fuente
1
Una adición útil a las respuestas existentes, pero valdría la pena señalar que esto hará que se vuelva mplayera llamar para cada archivo. Es importante si lo intentas, por ejemplo ... | xargs -I{} mplayer -shuffle {}: a pesar de esto, se reproducirá en un orden completamente determinista -shuffle.
1
Probablemente no sea la intención. xargsse usa principalmente con comandos que aceptan una lista de nombres de archivo (ejemplo sencillo:) rm, e intenta pasar tantos nombres de archivo como pueda caber en cada invocación, dividiéndose en múltiples invocaciones si es necesario. Puede ver la diferencia cuando usa un comando donde cada invocación es visible, como echo(el valor predeterminado): seq 0 100000 | xargsimprime todos los números del 0 al 23695 (específicos de la plataforma, pero eso es lo que sucede en mi sistema) en la primera línea, al 45539 en la línea 2, etc. Y tiene razón, para la mayoría de los comandos, no importará.
4

En macOS 10.12.x (Sierra), si tiene espacios en los nombres de archivos o subdirectorios, puede usar lo siguiente:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l
Byron Formwalt
fuente
2

Depende de (a) qué tan vinculado está al número 7 en lugar de, por ejemplo, Limones, y (b) si alguno de los nombres de sus archivos contiene nuevas líneas (y si está dispuesto a cambiarles el nombre si lo tienen).

Hay muchas formas de lidiar con eso, pero algunas de ellas son:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

El readbucle no funciona si los nombres de archivo contienen nuevas líneas; los otros funcionan correctamente incluso con líneas nuevas en los nombres (y mucho menos espacios). Por mi dinero, si tiene nombres de archivo que contienen una nueva línea, debe cambiar el nombre del archivo sin la nueva línea. El uso de comillas dobles alrededor del nombre del archivo es clave para que los bucles funcionen correctamente.

Si tiene GNU findy GNU xargs(o FreeBSD (* BSD?) O Mac OS X), también puede usar las opciones -print0y -0, como en:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

Esto funciona independientemente del contenido del nombre (los únicos dos caracteres que no pueden aparecer en un nombre de archivo son barra diagonal y NUL, y la barra diagonal no causa problemas en una ruta de archivo, por lo que usar NUL como delimitador de nombre cubre todo). Sin embargo, si necesita filtrar las primeras 6 entradas, necesita un programa que maneje 'líneas' terminadas por NUL en lugar de nueva línea ... y no estoy seguro de que haya alguna.

El primero es, con mucho, el más simple para el caso específico en cuestión; sin embargo, es posible que no se generalice para cubrir sus otros escenarios que aún no ha enumerado.

Jonathan Leffler
fuente
2

Sé que no estoy respondiendo la xargspregunta directamente, pero vale la pena mencionar findla -execopción.

Dado el siguiente sistema de archivos:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

El comando find se puede hacer para manejar el espacio en Dream Theater y King's X. Entonces, para encontrar a los bateristas de cada banda usando grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

En la -execopción se {}encuentra el nombre del archivo, incluida la ruta. Tenga en cuenta que no tiene que escapar o ponerlo entre comillas.

La diferencia entre -execlos terminadores ( +y \;) es que +agrupa tantos nombres de archivos como puede en una línea de comando. Mientras \;que ejecutará el comando para cada nombre de archivo.

Entonces, find bands/ -type f -exec grep Drums {} +resultará en:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

y find bands/ -type f -exec grep Drums {} \;dará como resultado:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

En el caso de que grepesto tenga el efecto secundario de imprimir el nombre del archivo o no.

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Por supuesto, greplas opciones -hy -Hcontrolarán si el nombre del archivo se imprime o no, independientemente de cómo grepse llame.


xargs

xargs También puede controlar cómo están los archivos man en la línea de comandos.

xargspor defecto agrupa todos los argumentos en una línea. Para hacer lo mismo que -exec \;sí usa xargs -l. Tenga en cuenta que la -topción le dice xargsque imprima el comando antes de ejecutarlo.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

Ver que el -l opción le dice a xargs que ejecute grep para cada nombre de archivo.

Contra el valor predeterminado (es decir, sin -lopción):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargstiene un mejor control sobre cuántos archivos pueden estar en la línea de comando. Dé a la -lopción el número máximo de archivos por comando.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

Ver que grepse ejecutó con dos nombres de archivo debido a -l2.

musaraña
fuente
1

Dado el título específico de esta publicación, aquí está mi sugerencia:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

La idea es convertir espacios en blanco a cualquier carácter único, como '<', y luego cambiarlo a '\', una barra invertida seguida de un espacio en blanco. Luego puede canalizar eso en cualquier comando que desee, como:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

La clave aquí radica en los comandos 'tr' y 'sed'; y puedes usar cualquier carácter además de '<', como '?' o incluso un tabulador.

Dick.Guertin
fuente
¿Para qué sirve el desvío tr? ¿Por qué no solo ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee
1
Descubrí que "tr '' '?'" Elimina la necesidad de "sed". El soltero "?" el carácter no está en blanco, pero coincide con CUALQUIER carácter individual, en este caso: en blanco. Las posibilidades de que sea otra cosa son bastante pequeñas y aceptables ya que está tratando de procesar TODOS los archivos que terminan en .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin
También puede manejar "tab" al mismo tiempo: tr '\ t' '??' maneja ambos.
Dick Guertin
1

Las soluciones alternativas pueden ser útiles ...

También puede agregar un carácter nulo al final de sus líneas usando Perl, luego use la -0opción en xargs. A diferencia de xargs -d '\ n' (en respuesta aprobada), esto funciona en todas partes, incluido OS X.

Por ejemplo, para enumerar recursivamente (ejecutar, mover, etc.) archivos MPEG3 que pueden contener espacios u otros caracteres divertidos, usaría:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(Nota: para el filtrado, prefiero la sintaxis "| grep" más fácil de recordar que los argumentos de "find" --name).

Charlie Dalsass
fuente