Xargs no segundo lado do tubo?

5

Estou tentando fazer o seguinte:

cat file1.txt | xargs -I{} "cat file2.txt | grep {}"

Espero que cada linha do arquivo1 seja o valor do grep no final do terceiro canal. Não está funcionando como esperado.

Isso ocorre porque -I{} deixa de procurar itens para substituir quando atinge o tubo? Existe uma maneira de contornar isso?

    
por Philip Kirkbride 22.04.2017 / 17:49

2 respostas

11

É porque você precisa de um shell para criar um canal ou executar um redirecionamento. Note que cat é o comando para concatenar, faz pouco sentido usá-lo apenas para um arquivo.

cat file1.txt | xargs -I{} sh -c 'cat file2.txt | grep -e "$1"' sh {}

Não faça :

cat file1.txt | xargs -I{} sh -c 'cat file2.txt | grep -e {}'

como isso equivaleria a uma vulnerabilidade de injeção de comando. O {} seria expandido no argumento code para sh , assim interpretado como código shell. Por exemplo, se a linha de file1.txt for $(reboot) , isso chamaria reboot .

O -e (ou você também pode usar -- ) também é importante. Sem isso, você teria problemas com regexps começando com - .

Você pode simplificar os redirecionamentos acima usando, em vez de cat :

< file1.txt xargs -I{} sh -c '< file2.txt grep -e "$1"' sh {}

Ou simplesmente passe os nomes dos arquivos como argumento para grep em vez de usar redirecionamentos. Nesse caso, você pode até mesmo descartar sh :

< file1.txt xargs -I{} grep -e {} file2.txt

Você também pode dizer a grep para procurar todos os regexps de uma única vez em uma única chamada:

grep -f file1.txt file2.txt

Note, no entanto, que nesse caso, isso é apenas uma expressão regular para cada linha de file1.txt , não há nenhum processamento especial de citações feito por xargs .

xargs , por padrão, considera sua entrada como uma lista de espaços em branco (com algumas implementações apenas espaço e tabulação, em outros, qualquer na classe de caractere [:blank:] da localidade atual) ou palavras separadas de nova linha para as quais barra invertida e aspas duplas podem ser usadas para escapar dos separadores (a nova linha só pode ser escapada pela barra invertida) ou um ao outro.

Por exemplo, em uma entrada como:

 'a "b'\" "bar baz" x\
y

xargs sem -I{} passaria a "b" , bar baz e x<newline>y para o comando.

Com -I{} , xargs obtém uma palavra por linha, mas ainda faz algum processamento extra. Ele ignora os espaços em branco iniciais (mas não à direita). Blanks não são mais considerados separadores, mas o processamento de cotações ainda está sendo feito.

Na entrada acima, xargs -I{} passaria um argumento a "b" foo bar x<newline>y para o comando. Observe também que muitos sistemas, conforme exigido pelo POSIX, não funcionarão se as palavras tiverem mais de 255 caracteres. Apesar de tudo, xargs -I{} é bastante inútil.

Se você quiser que cada linha seja passada verbatim como argumento para o comando, você pode usar o GNU xargs -d '\n' extension:

< file1.txt xargs -d '\n' -n 1 grep file2.txt -e

(aqui contando com outra extensão do GNU grep que permite passar opções após argumentos (desde que o POSIXly correto não esteja no ambiente) ou portável:

sed "s/'/'\\\''/g;s/.*/'&'/" file1.txt | xargs -n1 sh -c '
  for line do
    grep -e "$line" file2.txt
  done' sh

Se você quisesse que cada palavra em file1.txt (citações ainda reconhecidas) em oposição a cada linha fosse procurada (o que também funcionaria em torno do seu problema de espaço à direita se você tiver uma palavra por linha de qualquer maneira), você pode usar xargs -n1 sozinho em vez de usar -I :

< file1.txt xargs -n1 sh -c '
  for word do
    grep -e "$word" file2.txt
  done' sh

Para remover espaços em branco iniciais e finais (mas sem o processamento de cotações que xargs faz), você também pode fazer:

unset IFS # restore word splitting to its default
while read -r regexp; do
  grep -e "$regexp" file2.txt
done < file1.txt
    
por 22.04.2017 / 18:00
8

Dependendo do que você está tentando fazer, é melhor você ignorar xargs totalmente e, em vez disso, usar essa solução:

grep -f file1.txt file2.txt

Isto difere do seu comando original (uma vez que o consertamos como na resposta de Stéphane Chazelas) como segue:

  • As linhas são impressas na ordem em que aparecem em file2.txt , independentemente de quais padrões correspondem. Em seu comando, todas as linhas que correspondem ao primeiro padrão são impressas, depois todas as linhas que correspondem ao segundo e assim por diante.
  • As linhas que correspondem a mais de um padrão são impressas exatamente uma vez. Em seu comando, eles são impressos uma vez para cada padrão que correspondem.
  • Vários sinalizadores podem ser usados com mais facilidade, incluindo -v e -c .

A -f bandeira é especificada por POSIX e, portanto, razoavelmente portátil.

    
por 22.04.2017 / 21:45

Tags