O que o “xargs grep” faz?

25

Conheço o grepcomando e estou aprendendo sobre as funcionalidades do xargs, então leio esta página que fornece alguns exemplos de como usar o xargscomando.

Estou confuso com o último exemplo, exemplo 10. Ele diz "O comando xargs executa o comando grep para encontrar todos os arquivos (entre os arquivos fornecidos pelo comando find) que continham uma string 'stdlib.h'"

$ find . -name '*.c' | xargs grep 'stdlib.h'
./tgsthreads.c:#include
./valgrind.c:#include
./direntry.c:#include
./xvirus.c:#include
./temp.c:#include
...
...
...

No entanto, qual é a diferença de simplesmente usar

$ find . -name '*.c' | grep 'stdlib.h'

?

Obviamente, ainda estou lutando com o que exatamente xargs está fazendo, então qualquer ajuda é apreciada!

Alfa Ômega
fonte
2
Esta pergunta pode ser útil: Quando o XArgs é necessário?
TheOdd 4/16

Respostas:

30
$ find . -name '*.c' | grep 'stdlib.h'

Isso canaliza a saída (stdout) * de findpara (stdin of) * grep 'stdlib.h' como texto (ou seja, os nomes dos arquivos são tratados como texto). grepfaz o que é habitual e encontra as linhas correspondentes neste texto (qualquer nome de arquivo que contenha o padrão). O conteúdo dos arquivos nunca é lido.

$ find . -name '*.c' | xargs grep 'stdlib.h'

Isso constrói um comando grep 'stdlib.h' para o qual cada resultado findé um argumento - portanto, ele procurará correspondências dentro de cada arquivo encontrado por find( xargspode-se pensar em transformar seu stdin em argumentos para os comandos fornecidos) *

Use -type fno seu comando find ou você obterá erros nos grepdiretórios correspondentes. Além disso, se os nomes de arquivos tiverem espaços, xargseles estragarão muito, então use o separador nulo adicionando -print0e xargs -0para obter resultados mais confiáveis:

find . -type f -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

* adicionou esses pontos explicativos extras, como sugerido no comentário por @cat

Zanna
fonte
2
você pode considerar observar (porque parece que você omitiu) o ponto-chave que |canaliza stdout para stdin do grep, que não é o mesmo que os argumentos do grep e fornece resultados confusos.
Cat #
1
Ou use o GNU find's find -name '*.c' -exec grep stdlib.h {} +. Eu praticamente nunca uso xargs. Também fiquei surpreso que ninguém mencionou que o xargs serve a um propósito semelhante para grep $(find)comandar a substituição, então escrevi uma resposta minha. Explicar xargs como substituição de comando com menos limitações e problemas parece natural.
Peter Cordes
Uma situação em que uso o xargs é se estou excluindo muitos arquivos como resultado da localização. Se você apenas executar -exec rm, ele executará rm em cada arquivo, um de cada vez, o que é muito ineficiente. A tubulação para xargs fará todos de uma só vez com uma rm. Limitar com say -n50 (50 de cada vez) pode impedir o estouro da linha de comando (problema com muitos arquivos).
Lsd #
1
@lsd: Por que não find -deletepara esse caso especial? Ou para comandos diferentes de rm, se você encontrar o GNU, -exec some_command {} +agrupe-os em lotes como xargs, em vez disso, o \;comportamento de executar o comando é separado para cada um.
Peter Cordes
O @lsd findexecuta o comando em cada arquivo se, e somente se, estiver usando -exec command \;Both xargse -exec command \+chamará o comando com o número máximo de argumentos permitidos pelo sistema. Em outras palavras, eles são equivalentes #
Sergiy Kolodyazhnyy
6

O xargs pega sua entrada padrão e a transforma em argumentos da linha de comando.

find . -name '*.c' | xargs grep 'stdlib.h' é muito parecido com

grep 'stdlib.h' $(find . -name '*.c')  # UNSAFE, DON'T USE

E fornecerá os mesmos resultados, desde que a lista de nomes de arquivos não seja muito longa para uma única linha de comando. (O Linux suporta megabytes de texto em uma única linha de comando; portanto, geralmente você não precisa de xargs.)


Mas ambos são péssimos, porque quebram se os nomes de arquivos contiverem espaços . Em vez disso, find -print0 | xargs -0funciona, mas o mesmo acontece

find . -name '*.c' -exec grep 'stdlib.h' {} +

Isso nunca canaliza os nomes de arquivos em qualquer lugar: findagrupa-os em uma grande linha de comando e executa grepdiretamente.

\;em vez de +executar grep separadamente para cada arquivo, o que é muito mais lento. Não faça isso. Mas +é uma extensão do GNU, então você precisa xargsfazer isso com eficiência se não puder assumir a localização do GNU.


Se você deixar de fora xargs, find | grepo padrão será comparado à lista de nomes de arquivos findimpressos.

Então, nesse ponto, é melhor que você faça find -name stdlib.h. Obviamente, com -name '*.c' -name stdlib.h, você não obterá nenhuma saída porque esses padrões não podem corresponder e o comportamento padrão da localização é AND as regras juntas.

Substitua lessem qualquer ponto do processo para ver qual saída qualquer parte do pipeline produz.


Outras leituras: http://mywiki.wooledge.org/BashFAQ tem ótimas coisas.

Peter Cordes
fonte
1
O GNU xargs também precisa -ddefinir o separador, para que você possa -d'\n'manipular uma lista separada por nova linha, o que pode ser útil se você manipular uma lista de nomes de arquivos em um arquivo, etc. (desde que os nomes dos arquivos não tenham novas linhas em si, que é).
ilkkachu
@ilkkachu: sim, novas linhas em nomes de arquivos são muito mais raras que espaços, uma vez que quebram a maioria dos scripts. myfunc(){ local IFS=$'\n'; fgrep stdlib.h` $ (find) ; }também funciona com o mesmo efeito. Ou, como uma linha, um (IFS=...; cmd...)subshell também funciona para conter a alteração no IFS sem precisar salvá-lo / restaurá-lo.
Peter Cordes
@ PeterCordes Por favor, não faça command $( find )coisas desse tipo. Nomes de arquivos problemáticos com espaços e caracteres especiais podem quebrar esse tipo de coisa. No mínimo, aspas duplas a substituição do comando.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy: Obrigado por apontar que parece que eu estou realmente recomendando isso. As pessoas que pesquisam podem ter copiado / colado isso em vez de ler a próxima seção. Atualizado para resolver isso.
Peter Cordes
@SergiyKolodyazhnyy: Ou você estava respondendo ao meu comentário? Observe que eu defino, IFSentão é equivalente a usar xargs '-d\n'. A expansão de globos e o processamento de metacaracteres do shell ocorrem antes dos efeitos da substituição de comandos, então acho que é seguro mesmo com nomes de arquivos que contenham $()ou >. Concordou que o uso da divisão de palavras na substituição de comandos não é uma boa prática, exceto para o uso interativo único, onde você sabe algo sobre os nomes de arquivos. Mas command "$(find)"só é útil se você espera que ele produzir exatamente 1 filename ...
Peter Cordes
5

Em geral, xargsé usado para casos em que você canaliza (com o símbolo |) algo de um comando para outro ( Command1 | Command2), mas a saída do primeiro comando não é recebida corretamente como entrada do segundo comando.

Isso normalmente acontece quando o segundo comando não manipula a entrada de dados através da Entrada Padrão (stdin) corretamente (por exemplo: Várias linhas como entrada, a forma como as linhas são configuradas, os caracteres usados ​​como entrada, vários parâmetros como entrada, o tipo de dados recebido como entrada, etc.). Para dar um exemplo rápido, teste o seguinte:

Exemplo 1:

ls | echo- Isso não fará nada, pois echonão sabe como lidar com a entrada que está recebendo. Agora, neste caso, se a usarmos, xargsela processará a entrada de uma maneira que possa ser manipulada corretamente por echo( por exemplo: Como uma única linha de informação)

ls | xargs echo- Isso produzirá todas as informações lsem uma única linha

Exemplo 2:

Digamos que eu tenha vários arquivos goLang dentro de uma pasta chamada go. Eu procuraria por eles com algo assim:

find go -name *.go -type f | echo- Mas se o símbolo do cano ali e echono final não funcionasse.

find go -name *.go -type f | xargs echo- Aqui funcionaria graças a, xargsmas se eu quisesse cada resposta do findcomando em uma única linha, faria o seguinte:

find go -name *.go -type f | xargs -0 echo- Nesse caso, a mesma saída de findseria mostrada por echo.

Comandos como cp, echo, rm, lesse outros que precisam de uma maneira melhor de lidar com a entrada são beneficiados quando usados xargs.

Luis Alvarado
fonte
4

xargs é usado para gerar automaticamente argumentos de linha de comando com base (geralmente) em uma lista de arquivos.

Portanto, considerando algumas alternativas ao uso do xargscomando a seguir :

find . -name '*.c' -print0 | xargs -0 grep 'stdlib.h'

Há vários motivos para usá-lo em vez de outras opções que não foram mencionadas originalmente em outras respostas:

  1. find . -name '*.c' -exec grep 'stdlib.h' {}\;irá gerar um grepprocesso para cada arquivo - isso geralmente é considerado uma prática ruim e pode sobrecarregar o sistema se houver muitos arquivos encontrados.
  2. Se houver muitos arquivos, um grep 'stdlib.h' $(find . -name '*.c')comando provavelmente falhará, porque a saída da $(...)operação excederá o comprimento máximo da linha de comando do shell

Conforme mencionado em outras respostas, a razão para usar o -print0argumento findneste cenário e o -0xargs é que nomes de arquivos com certos caracteres (por exemplo, aspas, espaços ou até novas linhas) ainda sejam manipulados corretamente.

Michael Firth
fonte