Faça xargs manipular nomes de arquivos que contêm espaços

252
$ ls *mp3 | xargs mplayer  

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

Exiting... (End of file)  

Meu comando falha porque o arquivo "Lemon Tree.mp3" contém espaços e, portanto, o xargs pensa que são dois arquivos. Posso fazer o find + xargs funcionar com nomes de arquivos como este?

showkey
fonte
Em vez de ls |grep mp3 |sed -n "7p"você pode apenas usar echo "Lemon Tree.mp3".
Micha Wiedenmann
Esta pergunta também é respondida por stackoverflow.com/a/33528111/94687
imz - Ivan Zakharyaschev 11/17/17

Respostas:

256

O xargscomando usa caracteres de espaço em branco (tabulações, espaços, novas linhas) como delimitadores. Você pode reduzi-lo apenas para os novos caracteres de linha ('\ n') com a -dopção como esta:

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

Funciona apenas com GNU xargs. Para sistemas BSD, use a -0opção como esta:

ls *.mp3 | xargs -0 mplayer

Este método é mais simples e funciona com o GNU xargs também.

Raio
fonte
6
Melhor resposta para uso geral! Isso funciona mesmo se o comando anterior não é "encontrar"
nexayq
28
Infelizmente, esta opção não está disponível no OS X.
Thomas Tempelmann 14/11
25
@Thomas Para OS X, a bandeira é -E, ex:xargs -E '\n'
30
No OS X, -E '\ n' não teve efeito para mim, nem seria de esperar, pois modificou o eofstr e não o separador de registros. No entanto, consegui utilizar o sinalizador -0 como uma solução, mesmo que o comando anterior não seja 'find', simulando o efeito do sinalizador -print0 do find na minha entrada, por exemplo: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker
10
Para OS X, você pode "Brew instalar findutils", que lhe dá o comando "gxargs" que faz tem a opção -d.
Tom De Leu
213

O utilitário xargs lê cadeias delimitadas por espaço, tabulação, nova linha e fim de arquivo a partir da entrada padrão e executa o utilitário com as cadeias como argumentos.

Você deseja evitar o uso do espaço como delimitador. Isso pode ser feito alterando o delimitador para xargs. De acordo com o 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).

Tal como:

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

Para responder à pergunta sobre tocar o sétimo mp3; é mais simples de executar

 mplayer "$(ls *.mp3 | sed -n 7p)"
Jens
fonte
10
Isso está usando GNU finde GNU xargs; nem todas as versões desses programas suportam essas opções (embora exista um argumento a ser feito).
Jonathan Leffler
1
@JonathanLeffler s / GNU / FreeBSD / g; Infelizmente, o POSIX tem medo de NUL caracteres em arquivos de texto e ainda não recebeu terapia suficiente :-) Na verdade, meu conselho é usar opções não portáteis.
Jens
6
E o Mac OS X (um derivado do BSD) tem findcom -print0e xargscom -0. No entanto, AFAIK, HP-UX, AIX e Solaris não o fazem (mas devo ser corrigido: o HP-UX 11i não; Solaris 10 não; AIX 5.x não; mas não são as versões atuais ) Não seria difícil mudar sed, por exemplo, usar 'linhas' terminando com e '\0'não '\n', e o POSIX 2008 getdelim()facilitaria o gerenciamento.
Jonathan Leffler
2
+1 + 1 truque para usar com caminhos de arquivos que contêm arquivos de lista: cat $ file_paths_list_file | perl -ne's | \ n | \ 000 | g; print '| xargs -0 zip $ zip_package
Yordan Georgiev
2
Boa idéia para substituir as novas linhas por NUL - eu tive que fazer isso em um sistema incorporado que não tenha o GNU find nem o GNU xargs nem o perl - mas o comando tr pode ser aproveitado para fazer o mesmo: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson
28

Experimentar

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

ao invés de

ls | grep mp3 
Scott C Wilson
fonte
16

O xargs no MacOS não possui a opção -d, portanto, esta solução usa -0.

Obtenha ls para gerar um arquivo por linha, depois converta novas linhas em nulos e diga ao xargs para usar nulos como delimitador:

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

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

Isso ajudou no meu caso a excluir arquivos diferentes com espaços. Deve funcionar também com o mplayer. O truque necessário são as aspas. (Testado no Linux Xubuntu 14.04.)

Bendel
fonte
7

A resposta de Dick.Guertin [1] sugeriu que alguém poderia escapar dos espaços em um nome de arquivo é uma alternativa valiosa para outras soluções sugeridas aqui (como usar um caractere nulo como separador em vez de espaço em branco). Mas poderia ser mais simples - você realmente não precisa de um personagem único. Você pode simplesmente adicionar os espaços escapados diretamente:

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

Além disso, o grep é necessário apenas se você quiser apenas arquivos com espaços nos nomes. Mais genericamente (por exemplo, ao processar um lote de arquivos, alguns dos quais têm espaços, outros não), basta pular o grep:

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

Então, é claro, o nome do arquivo pode ter outro espaço em branco além de espaços em branco (por exemplo, uma guia):

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

Isso pressupõe que você tenha um sed que suporte -r (regex estendido), como GNU sed ou versões recentes do bsd sed (por exemplo, FreeBSD que originalmente soletrou a opção "-E" antes do FreeBSD 8 e suporta -r & -E para compatibilidade pelo FreeBSD 11, pelo menos). Caso contrário, você pode usar uma expressão básica de colchete de classe de caracteres regex e inserir manualmente os caracteres de espaço e tabulação nos []delimitadores.

[1] Talvez seja mais apropriado como comentário ou edição para essa resposta, mas no momento não tenho reputação suficiente para comentar e só posso sugerir edições. Como as últimas formas acima (sem o grep) alteram o comportamento da resposta original de Dick.Guertin, uma edição direta talvez não seja apropriada.

Juan
fonte
1
caras malucos do Unix que executam scripts que nomeiam arquivos sem considerar sua saída, é quem
andrew lorien
4

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

Observe que no comando acima, xargsserá chamado mplayernovamente para cada arquivo. Isso pode ser indesejável mplayer, mas pode ser bom para outros destinos.

Acumenus
fonte
1
Uma adição útil às respostas existentes, mas vale a pena notar que isso fará com mplayerque seja chamado novamente para cada arquivo. É importante se você tentar, por exemplo ... | xargs -I{} mplayer -shuffle {}: apesar de tudo, isso será executado em uma ordem completamente determinística -shuffle.
1
Provavelmente, geralmente não é a intenção. xargsé usado principalmente com comandos que aceitam uma lista de nomes de arquivos (exemplo fácil:) rme tenta passar quantos nomes de arquivos puderem caber em cada invocação, dividindo-os em várias invocações, se necessário. Você pode ver a diferença ao usar um comando em que cada chamada é visível, como echo(o padrão): seq 0 100000 | xargsimprime todos os números de 0 a 23695 (específico da plataforma, mas é o que acontece no meu sistema) na primeira linha, para 45539 na linha 2, etc. E você está certo, para a maioria dos comandos, isso não importa.
4

No macOS 10.12.x (Sierra), se você tiver espaços em nomes ou subdiretórios de arquivos, poderá usar o seguinte:

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

Depende de (a) quão apegado você está ao número 7, em vez de, digamos, limões, e (b) se algum dos seus nomes de arquivo contém novas linhas (e se você deseja renomeá-lo, se houver).

Há muitas maneiras de lidar com isso, mas algumas são:

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

O readloop não funciona se os nomes dos arquivos contiverem novas linhas; os outros funcionam corretamente mesmo com novas linhas nos nomes (sem falar em espaços). Pelo meu dinheiro, se você tiver nomes de arquivos contendo uma nova linha, renomeie o arquivo sem a nova linha. Usar aspas duplas ao redor do nome do arquivo é a chave para que os loops funcionem corretamente.

Se você possui GNU finde GNU xargs(ou FreeBSD (* BSD?) Ou Mac OS X), também pode usar as opções -print0e -0, como em:

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

Isso funciona independentemente do conteúdo do nome (os dois únicos caracteres que não podem aparecer em um nome de arquivo são barra e NUL, e a barra não causa problemas no caminho do arquivo, portanto, usar NUL como o delimitador de nome cobre tudo). No entanto, se você precisar filtrar as 6 primeiras entradas, precisará de um programa que lide com 'linhas' terminadas por NUL em vez de nova linha ... e não tenho certeza de que haja alguma.

O primeiro é de longe o mais simples para o caso específico em questão; no entanto, pode não ser generalizado para cobrir seus outros cenários que você ainda não listou.

Jonathan Leffler
fonte
2

Sei que não estou respondendo xargsdiretamente à pergunta, mas vale a pena mencionar finda -execopção.

Dado o seguinte sistema de arquivos:

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

0 directories, 4 files

O comando find pode ser feito para lidar com o espaço no Dream Theater e no King's X. Portanto, para encontrar os 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

No -exec opção, {}está o nome do arquivo, incluindo o caminho. Observe que você não precisa escapá-lo ou colocá-lo entre aspas.

A diferença entre -execos terminadores ( +e\; ) é que +agrupa quantos nomes de arquivos puderem em uma linha de comando. Considerando que \;irá executar o comando para cada nome de arquivo.

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

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

e find bands/ -type f -exec grep Drums {} \; resultará em:

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

No caso de grep tem o efeito colateral de imprimir ou não o nome do arquivo.

[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

Obviamente, grepas opções de -he -Hcontrolarão se o nome do arquivo é ou não impresso, independentemente de como grepé chamado.


xargs

xargs também pode controlar como os arquivos man estão na linha de comando.

xargspor padrão, agrupa todos os argumentos em uma linha. Para fazer a mesma coisa que -exec \;usa xargs -l. Observe que a -topção informa xargspara imprimir o comando antes de executá-lo.

[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

Veja que o -l opção diz ao xargs para executar grep para cada nome de arquivo.

Versus o padrão (ou seja, nenhuma -lopção):

[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

xargstem melhor controle sobre quantos arquivos podem estar na linha de comando. Dê à -lopção o número máximo de arquivos 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]# 

Veja que grepfoi executado com dois nomes de arquivos por causa de -l2.

shrewmouse
fonte
1

Dado o título específico deste post, aqui está minha sugestão:

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

A idéia é converter espaços em branco em qualquer caractere exclusivo, como '<', e depois alterá-lo para '\', uma barra invertida seguida por um espaço em branco. Você pode canalizar isso para qualquer comando que desejar, como:

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

A chave aqui está nos comandos 'tr' e 'sed'; e você pode usar qualquer caractere além de '<', como '?' ou mesmo um caractere de tabulação.

Dick.Guertin
fonte
Para que serve o desvio tr? Por que não apenas ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
Tripleee
1
Eu descobri que "tr '' '?'" Elimina a necessidade de "sed". O solteiro "?" O caractere não está em branco, mas corresponde a QUALQUER caractere único, neste caso: em branco. As chances de ser outra coisa são muito pequenas e aceitáveis, pois você está tentando processar TODOS os arquivos que terminam em .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin
Você também pode manipular "tab" ao mesmo tempo: tr '\ t' '??' lida com ambos.
Dick Guertin
1

Soluções alternativas podem ser úteis ...

Você também pode adicionar um caractere nulo ao final de suas linhas usando Perl e, em seguida, usar a -0opção em xargs. Diferente do xargs -d '\ n' (na resposta aprovada) - isso funciona em qualquer lugar, incluindo o OS X.

Por exemplo, para listar recursivamente (executar, mover etc.) arquivos MPEG3 que podem conter espaços ou outros caracteres engraçados - eu usaria:

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

(Nota: para filtrar, prefiro a sintaxe "| grep" mais fácil de lembrar do que os argumentos "find's" --name.)

Charlie Dalsass
fonte