Quando é necessário xargs?

128

O comando xargs 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 a alteração do comando para touch exigirá xargs :

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

$ \ls | grep Cases | xargs touch
    
por Zaid 19.11.2011 / 17:34

6 respostas

136

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

Se você acabou de usar um pipe, ele recebe dados em STDIN (o fluxo de entrada padrão) como uma pilha de dados que pode ser classificada em uma linha por vez. No entanto, alguns programas não aceitam seus comandos no padrão, eles esperam que seja soletrado nos argumentos para o comando. Por exemplo, touch recebe 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 no padrão e deseja usá-los como argumentos para touch , você precisa usar xargs , que lê o nome STDIN stream data e converte cada linha em espaços separados por argumentos para o comando.

Essas duas coisas são equivalentes:

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

Não use xargs a menos que você saiba exatamente o que está fazendo e por que é necessário. É bem comum que haja uma maneira melhor de fazer o trabalho do que usar xargs para forçar a conversão. O processo de conversão também é repleto de possíveis armadilhas, como fuga e expansão de palavras, etc.

    
por 19.11.2011 / 17:40
65

Para expandir as respostas já fornecidas, xargs pode fazer uma coisa interessante que está se tornando cada vez mais importante no cenário atual de computação distribuída e com vários núcleos: pode paralelizar trabalhos 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 de uma só vez ( -P 3 ).

    
por 19.11.2011 / 22:02
24

xargs é particularmente útil quando você tem uma lista de caminhos de arquivos em stdin e quer 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 echo antes de 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 argumento -n 1 fará com que os xargs transformem cada linha em um comando próprio. O comando sed -i "s/color/colour/g" substituirá todas as ocorrências de color por colour do arquivo especificado.

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

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

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

Isso funciona com qualquer comando que produza nomes de arquivos como saída, como find ou locate . Se acontecer de você usá-lo em um repositório git com muitos arquivos, pode ser mais eficiente usá-lo com git grep -l em vez de git ls-files , assim:

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

O comando git grep -l "color" "*.tex" dará uma lista de arquivos "* .tex" contendo a frase "cor".

    
por 19.11.2011 / 21:54
6

Seu primeiro argumento ilustra bem a diferença.

\ls | grep Cases | less permite procurar na lista de nomes de arquivos produzidos por ls e grep . Não importa que eles sejam nomes de arquivos, são apenas alguns textos.

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

Ao considerar o uso de xargs , lembre-se de que ele espera uma entrada formatada de maneira estranha: delimitado por espaços em branco, com \ , ' e " usado para citar (de maneira incomum, porque \ não é especial entre aspas). Use somente xargs se seus nomes de arquivo não contiverem espaço em branco ou \'" .

    
por 20.11.2011 / 01:45
4

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

Exatamente o que você deseja usar find :

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

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

Ambos find e xargs são difíceis de aprender no começo. Para ajudá-lo a aprender xargs , tente usar a opção -p ou --interactive , que mostrará o comando que está prestes a executar e perguntará se deseja ou não executá-lo.

Da mesma forma, com find , você pode usar -ok no lugar de -exec para perguntar se deseja ou não executar o comando.

Há momentos em que find não poderá fazer tudo o que você quer e é aí que entra xargs . O comando -exec aceitará apenas uma instância de {} aparecendo. Se você receber um erro com find -type f -exec cp {} {}.bak \; , você pode fazer isso da seguinte forma: find -type f -print0 | xargs -0 -l1 -IX cp X X.bak

Você pode aprender mais sobre Executar comandos no Manual do GNU Findutils .

Além disso, mencionei que find faz com segurança o que você deseja, porque quando você estiver lidando com arquivos, encontrará espaços e outros caracteres que causarão problemas com xargs , a menos que você use -0 ou --null opção junto com algo que gera itens de entrada terminados por um caractere nulo em vez de espaço em branco.

    
por 23.11.2011 / 16:36
1

xargs (junto com find , sort , du , uniq , perl e alguns outros) aceita uma opção de linha de comando para dizer "STDIN tem uma lista de arquivos, separados por um byte NUL (0x00) ". Isso facilita a manipulação de nomes de arquivos com espaços e outros personagens engraçados neles. Nomes de arquivo não contêm NULs.

    
por 20.11.2011 / 00:05