Faire en sorte que xargs gère les noms de fichiers contenant des espaces

252
$ ls *mp3 | xargs mplayer  

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

Exiting... (End of file)  

Ma commande échoue car le fichier "Lemon Tree.mp3" contient des espaces et donc xargs pense qu'il s'agit de deux fichiers. Puis-je faire fonctionner find + xargs avec des noms de fichiers comme celui-ci?

Afficher la touche
la source
Au lieu de ls |grep mp3 |sed -n "7p"vous pouvez simplement utiliser echo "Lemon Tree.mp3".
Micha Wiedenmann
1
Copie
imz - Ivan Zakharyaschev
Cette question est également répondue par stackoverflow.com/a/33528111/94687
imz - Ivan Zakharyaschev

Réponses:

256

La xargscommande prend des caractères d'espace blanc (tabulations, espaces, nouvelles lignes) comme délimiteurs. Vous pouvez le réduire uniquement pour les nouveaux caractères de ligne ('\ n') avec une -doption comme celle-ci:

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

Il ne fonctionne qu'avec les xargs GNU. Pour les systèmes BSD, utilisez l' -0option comme ceci:

ls *.mp3 | xargs -0 mplayer

Cette méthode est plus simple et fonctionne également avec les xargs GNU.

Rayon
la source
6
Meilleure réponse pour une utilisation générale! Cela fonctionne même si votre commande précédente n'est pas "find"
nexayq
28
Malheureusement, cette option n'est pas disponible sur OS X.
Thomas Tempelmann
25
@Thomas Pour OS X, le drapeau est -E, par exemple:xargs -E '\n'
30
Sous OS X, -E '\ n' n'a pas eu d'effet pour moi, et je ne m'attendrais pas à ce qu'il modifie l'eofstr et non le séparateur d'enregistrement. Cependant, j'ai pu utiliser le drapeau -0 comme solution, même si la commande précédente n'est pas "find", en simulant l'effet du drapeau -print0 de find dans mon entrée, par exemple: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker
10
Pour OS X, vous pouvez « brasser » installer findutils, ce qui vous donne la commande « gxargs » qui fait que le commutateur -d.
Tom De Leu
213

L'utilitaire xargs lit l'espace, la tabulation, la nouvelle ligne et les chaînes délimitées à la fin du fichier à partir de l'entrée standard et exécute l'utilitaire avec les chaînes comme arguments.

Vous voulez éviter d'utiliser l'espace comme délimiteur. Cela peut être fait en modifiant le délimiteur pour xargs. Selon le manuel:

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

Tel que:

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

Pour répondre à la question sur la lecture du septième mp3; il est plus simple d'exécuter

 mplayer "$(ls *.mp3 | sed -n 7p)"
Jens
la source
10
Cela utilise GNU findet GNU xargs; toutes les versions de ces programmes ne prennent pas en charge ces options (bien qu'il soit nécessaire de le faire).
Jonathan Leffler
1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX a malheureusement peur des caractères NUL dans les fichiers texte et n'a pas encore eu assez de thérapie :-) Mes conseils ont en fait recours à des options non portables.
Jens
6
Et Mac OS X (un dérivé BSD) a findavec -print0et xargsavec -0. AFAIK, HP-UX, AIX et Solaris ne le font pas cependant (mais je pense que ce n'est pas le cas: HP-UX 11i non; Solaris 10 non; AIX 5.x non; mais ce ne sont pas des versions actuelles ). Il ne serait pas difficile de changer sed, par exemple, d'utiliser des «lignes» se terminant par '\0'au lieu de '\n', et le POSIX 2008 getdelim()le rendrait facile à gérer.
Jonathan Leffler
2
+1 + 1 astuce pour l'utilisation avec des chemins de fichier contenant des fichiers de liste: cat $ file_paths_list_file | perl -ne's | \ n | \ 000 | g; print '| xargs -0 zip $ zip_package
Yordan Georgiev
2
Bonne idée de remplacer les sauts de ligne par NUL - je devais le faire sur un système embarqué qui n'avait ni GNU find ni GNU xargs ni perl - mais la commande tr peut être utilisée pour faire de même: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson
28

Essayer

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

au lieu de

ls | grep mp3 
Scott C Wilson
la source
16

xargs sur MacOS n'a pas l'option -d, donc cette solution utilise plutôt -0.

Demandez à ls de générer un fichier par ligne, puis traduisez les sauts de ligne en valeurs nulles et dites à xargs d'utiliser des valeurs nulles comme délimiteur:

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

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

Cela a aidé dans mon cas à supprimer différents fichiers avec des espaces. Cela devrait aussi fonctionner avec mplayer. L'astuce nécessaire est les guillemets. (Testé sur Linux Xubuntu 14.04.)

Bendel
la source
7

La réponse de Dick.Guertin [1] a suggéré que l'on pourrait échapper aux espaces dans un nom de fichier est une alternative valable aux autres solutions suggérées ici (comme utiliser un caractère nul comme séparateur plutôt que des espaces). Mais cela pourrait être plus simple - vous n'avez pas vraiment besoin d'un personnage unique. Vous pouvez simplement demander à sed d'ajouter directement les espaces échappés:

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

De plus, le grep n'est nécessaire que si vous ne voulez que des fichiers avec des espaces dans les noms. De manière plus générique (par exemple, lors du traitement d'un lot de fichiers dont certains ont des espaces, d'autres non), il suffit de sauter le grep:

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

Ensuite, bien sûr, le nom de fichier peut avoir d'autres espaces que des blancs (par exemple, un onglet):

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

Cela suppose que vous avez un sed qui prend en charge -r (regex étendu) tel que GNU sed ou les versions récentes de bsd sed (par exemple, FreeBSD qui orthographiait à l'origine l'option "-E" avant FreeBSD 8 et prend en charge à la fois -r et -E pour la compatibilité via FreeBSD 11 au moins). Sinon, vous pouvez utiliser une expression de parenthèse de classe de caractères regex de base et entrer manuellement les espaces et les tabulations dans les []délimiteurs.

[1] C'est peut-être plus approprié en tant que commentaire ou modification de cette réponse, mais pour le moment je n'ai pas assez de réputation pour commenter et je ne peux que suggérer des modifications. Étant donné que les dernières formes ci-dessus (sans le grep) modifient le comportement de la réponse originale de Dick.Guertin, une modification directe n'est peut-être pas appropriée de toute façon.

Juan
la source
1
les gars unix fous qui exécutent des scripts qui nomment des fichiers sans tenir compte de leur sortie, c'est qui
andrew lorien
4

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

Notez que dans la commande ci-dessus, xargsappellera à mplayernouveau pour chaque fichier. Cela peut être indésirable pour mplayer, mais peut être acceptable pour d'autres cibles.

Acumenus
la source
1
Un complément utile aux réponses existantes, mais il convient de noter que cela entraînera mplayerun nouvel appel pour chaque fichier. C'est important si vous essayez par exemple ... | xargs -I{} mplayer -shuffle {}: cela jouera dans un ordre complètement déterministe, malgré -shuffle.
1
Ce n'est probablement pas l'intention. xargsest principalement utilisé avec des commandes qui acceptent une liste de noms de fichiers (exemple simple:) rm, et tente de transmettre autant de noms de fichiers qu'il peut en contenir dans chaque appel, en ne le fractionnant qu'en plusieurs appels si nécessaire. Vous pouvez voir la différence lorsque vous utilisez une commande où chaque appel est visible, comme echo(par défaut): seq 0 100000 | xargsimprime tous les nombres de 0 à 23695 (spécifique à la plate-forme, mais c'est ce qui se passe sur mon système) sur la première ligne, à 45539 sur la ligne 2, etc. Et vous avez raison, pour la plupart des commandes, cela n'a pas d'importance.
4

Sur macOS 10.12.x (Sierra), si vous avez des espaces dans les noms de fichiers ou sous-répertoires, vous pouvez utiliser les éléments suivants:

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

Cela dépend de (a) à quel point vous êtes attaché au numéro 7 par opposition, par exemple, aux citrons, et (b) si l'un de vos noms de fichiers contient des nouvelles lignes (et si vous êtes prêt à les renommer s'ils le font).

Il existe de nombreuses façons d'y faire face, mais certaines d'entre elles sont:

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

La readboucle ne fonctionne pas si les noms de fichiers contiennent des retours à la ligne; les autres fonctionnent correctement même avec des retours à la ligne dans les noms (sans parler des espaces). Pour mon argent, si vous avez des noms de fichiers contenant une nouvelle ligne, vous devez renommer le fichier sans la nouvelle ligne. L'utilisation des guillemets doubles autour du nom de fichier est la clé du bon fonctionnement des boucles.

Si vous avez GNU findet GNU xargs(ou FreeBSD (* BSD?), Ou Mac OS X), vous pouvez également utiliser les options -print0et -0, comme dans:

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

Cela fonctionne quel que soit le contenu du nom (les deux seuls caractères qui ne peuvent pas apparaître dans un nom de fichier sont la barre oblique et NUL, et la barre oblique ne pose aucun problème dans un chemin de fichier, donc l'utilisation de NUL comme délimiteur de nom couvre tout). Cependant, si vous devez filtrer les 6 premières entrées, vous avez besoin d'un programme qui gère les «lignes» terminées par NUL au lieu de la nouvelle ligne ... et je ne suis pas sûr qu'il y en ait.

Le premier est de loin le plus simple pour le cas spécifique en question; cependant, il peut ne pas se généraliser pour couvrir vos autres scénarios que vous n'avez pas encore répertoriés.

Jonathan Leffler
la source
2

Je sais que je ne réponds pas xargsdirectement à la question, mais cela vaut la peine de mentionner findl' -execoption de.

Étant donné le système de fichiers suivant:

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

0 directories, 4 files

La commande find peut être effectuée pour gérer l'espace dans Dream Theater et King's X. Ainsi, pour trouver les batteurs de chaque groupe à l'aide de 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

Dans l' -execoption {}représente le nom de fichier, y compris le chemin. Notez que vous n'avez pas à y échapper ou à le mettre entre guillemets.

La différence entre -execles terminateurs de ( +et \;) est qu'il +regroupe autant de noms de fichiers qu'il peut sur une seule ligne de commande. Tandis que \;va exécuter la commande pour chaque nom de fichier.

Donc, cela find bands/ -type f -exec grep Drums {} +se traduira par:

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

et find bands/ -type f -exec grep Drums {} \;se traduira par:

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

Dans ce cas, grepcela a pour effet secondaire d'imprimer ou non le nom de fichier.

[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

Bien sûr, greples options de -het -Hcontrôleront si le nom de fichier est imprimé ou non, quelle que soit la façon dont il grepest appelé.


xargs

xargs peut également contrôler la façon dont les fichiers man se trouvent sur la ligne de commande.

xargspar défaut regroupe tous les arguments sur une seule ligne. Afin de faire la même chose qui -exec \;utilise xargs -l. Notez que l' -toption indique xargsd'imprimer la commande avant de l'exécuter.

[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

Vérifiez que l' -loption indique à xargs d'exécuter grep pour chaque nom de fichier.

Par rapport à la valeur par défaut (c'est-à-dire sans -loption):

[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

xargscontrôle mieux le nombre de fichiers sur la ligne de commande. Donnez à l' -loption le nombre maximum de fichiers par commande.

[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]# 

Voir qui a grepété exécuté avec deux noms de fichiers à cause de -l2.

musaraigne
la source
1

Compte tenu du titre spécifique de cet article, voici ma suggestion:

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

L'idée est de convertir les blancs en n'importe quel caractère unique, comme «<», puis de le changer en «\», une barre oblique inverse suivie d'un blanc. Vous pouvez ensuite diriger cela vers n'importe quelle commande de votre choix, comme:

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

La clé réside ici dans les commandes «tr» et «sed»; et vous pouvez utiliser n'importe quel caractère à part '<', comme '?' ou même un caractère de tabulation.

Dick.Guertin
la source
Quel est le but du détour via tr? Pourquoi pas juste ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee
1
J'ai trouvé que "tr '' '?'" Élimine le besoin de "sed". Le seul "?" le caractère n'est pas vide, mais correspond à N'IMPORTE QUEL caractère unique, dans ce cas: vide. Les chances que ce soit autre chose sont assez petites et acceptables puisque vous essayez de traiter TOUS les fichiers se terminant par .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin
Vous pouvez également gérer "tab" en même temps: tr '\ t' '??' gère les deux.
Dick Guertin
1

Des solutions alternatives peuvent être utiles ...

Vous pouvez également ajouter un caractère nul à la fin de vos lignes à l'aide de Perl, puis utiliser l' -0option dans xargs. Contrairement au xargs -d '\ n' (dans la réponse approuvée) - cela fonctionne partout, y compris OS X.

Par exemple, pour lister récursivement (exécuter, déplacer, etc.) des fichiers MPEG3 qui peuvent contenir des espaces ou d'autres personnages amusants - j'utiliserais:

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

(Remarque: pour le filtrage, je préfère la syntaxe "| grep" plus facile à mémoriser aux arguments --name de "find".)

Charlie Dalsass
la source