Quando é necessário xargs?

134

O xargscomando sempre me confunde. Existe uma regra geral para isso?

Considere os dois exemplos abaixo:

$ \ls | grep Cases | less

imprime os arquivos que correspondem a 'Casos', mas alterar o comando para touchexigirá xargs:

$ \ls | grep Cases | touch
touch: missing file operand
Try `touch --help' for more information.

$ \ls | grep Cases | xargs touch
Zaid
fonte

Respostas:

143

A diferença está em quais dados o programa de destino está aceitando.

Se você usar apenas um canal, ele recebe dados no STDIN (o fluxo de entrada padrão) como uma pilha bruta de dados que podem ser classificados por uma linha por vez. No entanto, alguns programas não aceitam seus comandos no padrão, eles esperam que seja explicitado nos argumentos do comando. Por exemplo touchleva um nome de arquivo como um parâmetro na linha de comando da seguinte forma: touch file1.txt.

Se você tem um programa que gera nomes de arquivos na saída padrão e quiser usá-los como argumentos para touch, você tem que usar xargso que lê os dados de fluxo stdin e converte cada linha no espaço argumentos separados para o comando.

Essas duas coisas são equivalentes:

# touch file1.txt
# echo file1.txt | xargs touch

Não use a xargsmenos que saiba exatamente o que está fazendo e por que é necessário. Geralmente, existe uma maneira melhor de fazer o trabalho do que usar xargspara forçar a conversão. O processo de conversão também está cheio de possíveis armadilhas, como escape e expansão de palavras, etc.

Caleb
fonte
2
O aviso parece um pouco para mim. Das duas opções comuns para obter um fluxo em uma linha de comando ( xargse $(...)), xargs é muito mais seguro que a substituição de comando. E não me lembro de ter encontrado um nome de arquivo legítimo com uma nova linha. Os problemas de escape e expansão de palavras não são problemas com a substituição de comandos, e não xargs?
Camh
6
@camh: Eles são armadilhas potenciais com ambos. No shell, você precisa se preocupar com a divisão de nomes de arquivos em espaços, guias e novas linhas. No xargs, você só precisa se preocupar com novas linhas. No xargs, se sua saída estiver formatada corretamente, você poderá dividir palavras / nomes de arquivos no caractere NUL ( xargs -0), o que é útil em conjunto com find -print0.
Ken Bloom
Será que xargschamar o programa através do shell com espaço separado args, ou ele realmente construir a lista de argumentos internamente (por exemplo. Para uso com execv/ execp)?
detly
1
Ele o constrói internamente e usa o execvp, portanto é seguro. Além disso, o GNU xargs (como usado no Linux e em alguns outros) permite especificar uma nova linha como seu delimitador -d \n, embora o BSD xargs (OSX et al) não pareça suportar essa opção.
fofo
72

Expandir as respostas já fornecidas, xargspode fazer uma coisa interessante que está se tornando cada vez mais importante no cenário de computação multicore e distribuída de hoje: pode paralelizar tarefas de processo.

Por exemplo:

$ find . -type f -name '*.wav' -print0 |xargs -0 -P 3 -n 1 flac -V8

irá codificar * .wav => * .flac, usando três processos ao mesmo tempo ( -P 3).

anfetamaquina
fonte
Uau. Eu deveria saber disso há uma semana, quando estava fazendo exatamente a mesma coisa (exceto usando OGG) com 50 GiB de WAVs. :)
Alois Mahdal
por que não usar o parâmetro -exec encontrado?
Evgeny
3
@Evgeny O -execparâmetro não processará trabalhos paralelos.
Amphetamachine
É bom observar que o -0argumentoxargs faz com que considere o NULLcaractere como o delimitador do item de entrada. find -print0saída itens delimitados por NULL. Essa é uma prática recomendada para nomes de arquivos que podem conter espaços, aspas ou outros caracteres especiais.
Dan Dascalescu 6/01
24

O xargs é particularmente útil quando você tem uma lista de caminhos de arquivo no stdin e deseja fazer algo com eles. Por exemplo:

$ git ls-files "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

Vamos examinar este passo a passo:

$ git ls-files "*.tex"
tex/ch1/intro.tex
tex/ch1/motivation.tex
....

Em outras palavras, nossa entrada é uma lista de caminhos para os quais queremos fazer algo.

Para descobrir o que o xargs faz com esses caminhos, um bom truque é adicionar echoantes do seu comando, assim:

$ git ls-files "*.tex" | xargs -n 1 echo sed -i "s/color/colour/g"
sed -i "s/color/colour/g" tex/ch1/intro.tex
sed -i "s/color/colour/g" tex/ch1/motivation.tex
....

O -n 1argumento fará com que os xargs transformem cada linha em um comando próprio. O sed -i "s/color/colour/g"comando substituirá todas as ocorrências de colorcom colourpelo arquivo especificado.

Observe que isso só funciona se você não tiver espaços em seus caminhos. Se fizer isso, você deve usar caminhos terminados nulos como entrada para xargs passando a -0sinalização. Um exemplo de uso seria:

$ git ls-files -z "*.tex" | xargs -0 -n 1 sed -i "s/color/colour/g"

Que faz o mesmo que descrevemos acima, mas também funciona se um dos caminhos tiver um espaço nele.

Isso funciona com qualquer comando que produza nomes de arquivos como saída, como findou locate. No entanto, se você o usa em um repositório git com muitos arquivos, pode ser mais eficiente usá-lo em git grep -lvez de git ls-files:

$ git grep -l "color" "*.tex" | xargs -n 1 sed -i "s/color/colour/g"

O git grep -l "color" "*.tex"comando fornecerá uma lista de arquivos "* .tex" que contêm a frase "cor".

Sverre Rabbelier
fonte
1
É verdade, mas se você aprendeu isso, também deve aprender Por que repetir as práticas inadequadas da saída do find?
Curinga
6

Seu primeiro argumento ilustra bem a diferença.

\ls | grep Cases | lesspermite navegar na lista de nomes de arquivos produzidos por lse grep. Não importa que sejam nomes de arquivos, são apenas alguns textos.

\ls | grep Cases | xargs lesspermite procurar os arquivos cujos nomes são produzidos pela primeira parte do comando. xargspega uma lista de nomes de arquivos como entrada e um comando em sua linha de comandos e executa o comando com os nomes de arquivos em sua linha de comandos.

Ao considerar usar xargs, tenha em mente que espera entrada formatada de maneira estranha: espaço em branco-delimitado, com \, 'e "usado para citar (de uma maneira incomum, porque \não é citações dentro especiais). Use apenas xargsse os nomes dos seus arquivos não contiverem espaços em branco ou \'".

Gilles
fonte
@ Gilles: xargs tem a -0, --nullopção de contornar a questão dos espaços (é muito provável que eu tenha aprendido isso com você :), então suponho que você esteja se referindo a uma xargchamada sem opções , mas estou intrigado com sua referência às aspas. Você tem um link ou um exemplo a esse respeito? . .. (ps | xargs lessé um "truque" acessível +1 .. obrigado ..
Peter.O
4

No seu exemplo, você não precisa usar xargsnada, pois findfará exatamente e com segurança o que deseja fazer.

Exatamente o que você deseja usar findé:

find -maxdepth 1 -name '*Cases*' -exec touch {} +

Neste exemplo, -maxdepth 1significa apenas pesquisar no diretório atual, não desça em nenhum subdiretório; por padrão, o find irá procurar em todos os subdiretórios (que geralmente é o que você deseja), a menos que você o restrinja com maxdepth. O {}é o nome do arquivo que será substituído em seu lugar e +é um dos dois marcadores de fim de comando, sendo o outro ;. A diferença entre eles é que isso ;significa executar o comando em cada arquivo, um de cada vez, enquanto que +executar o comando em todos os arquivos de uma só vez. Observe, no entanto, que seu shell provavelmente tentará se interpretar ;; portanto, você precisará escapar dele com um \;ou outro ';'. Sim, findtem uma série de pequenos aborrecimentos como esse, mas seu poder mais do que compensa isso.

Ambos finde xargssão difíceis de aprender em primeiro lugar. Para ajudá-lo a aprender, xargstente usar a opção -pou, --interactiveque mostra o comando que está prestes a executar e pergunta se você deseja executá-lo ou não.

Da mesma forma que findvocê pode usar -okno lugar de -execpara perguntar se você deseja ou não executar o comando.

Porém, há momentos em findque não é possível fazer tudo o que você deseja e é aí que xargsentra. O -execcomando aceita apenas uma instância de {}exibição; portanto, se você receber um erro, find -type f -exec cp {} {}.bak \;poderá fazê-lo da seguinte maneira :find -type f -print0 | xargs -0 -l1 -IX cp X X.bak

Você pode aprender mais sobre comandos de execução no manual do GNU Findutils .

Mencionei também que findo que você quer com segurança faz porque, ao lidar com arquivos, encontrará espaços e outros caracteres que causarão problemas, a xargsmenos que você use a opção -0ou --nulljunto com algo que gere itens de entrada finalizados por um caractere nulo. do espaço em branco.

aculich
fonte
Os nomes de arquivos @Wildcard com espaços ou caracteres como 'ou "podem ser problemáticos, enquanto que lidarão com findesses casos sem problemas.
Aculich 04/12/19
Sim eu conheço. Veja minha resposta para a pergunta vinculada . Eu provavelmente deveria ter reformulado essa pergunta para uma afirmação no comentário acima ou adicionado a frase "Veja a pergunta ..." na frente. : D
Caractere curinga
1

xargs(junto com find, sort, du, uniq, perle alguns outros) aceita um parâmetro de linha de comando para dizer "STDIN tem uma lista de arquivos, separados por um byte NUL (0x00)". Isso facilita o manuseio de nomes de arquivos com espaços e outros personagens engraçados. Os nomes de arquivos não contêm NULs.

waltinator
fonte
2
Eu acho que você quer dizer "nomes de arquivos não podem conter nulos".
Amphetamachine