Executar um comando uma vez por linha de entrada canalizada?

162

Eu quero executar um comando java uma vez para cada correspondência de ls | grep pattern -. Nesse caso, acho que poderia fazer, find pattern -exec java MyProg '{}' \;mas estou curioso sobre o caso geral - existe uma maneira fácil de dizer "executar um comando uma vez para cada linha de entrada padrão"? (Em peixes ou bash.)

Xodarap
fonte

Respostas:

92

Isso é o que xargsfaz.

... | xargs command
Keith
fonte
25
Não é bem assim. printf "foo bar\nbaz bat" | xargs echo wheevai render whee foo bar baz bat. Talvez adicione as opções -Lou -n?
Jander
3
@ Jander A questão era bastante geral, então eu dei a ferramenta geral. É verdade que você terá que ajustar seu comportamento com opções, dependendo das circunstâncias específicas.
Keith
4
... tr '\ n' '\ 0' | xargs -0
vrdhn 17/02
7
como "as circunstâncias específicas que dão a resposta certa à pergunta". :)
mattdm
7
Se você quiser ver a maneira correta de fazer isso com o xargs, veja minha resposta abaixo.
Michael Goldshteyn
167

A resposta aceita tem a idéia certa, mas a chave é passar xargsa -n1chave, o que significa "Executar o comando uma vez por linha de saída:"

cat file... | xargs -n1 command

Ou, para um único arquivo de entrada, você pode evitar cattotalmente o canal e apenas seguir com:

<file xargs -n1 command
Michael Goldshteyn
fonte
1
Também de interesse é a capacidade xargsda não correr se stdinestiver vazia: --no-run-if-empty -r: Se a entrada padrão não contém quaisquer nonblanks, não execute o comando. Normalmente, o comando é executado uma vez, mesmo que não haja entrada. Esta opção é uma extensão GNU.
Ronan Jouchet 24/10/2015
4
Como você acessa a linha interna command?
BT
Este é o uso correto de xargs. Sem -n1, ele funciona apenas em comandos que tratam listas de parâmetros como várias invocações, o que nem todos fazem.
masterxilo
3
printf "foo bar \ nbaz morcego" | xargs -N1 eco whee splits por palavras e não por linhas
Gismo Ranas
112

No Bash ou em qualquer outro shell no estilo Bourne (ash, ksh, zsh,…):

while read -r line; do command "$line"; done

read -rlê uma única linha da entrada padrão ( readsem -rinterpretar barras invertidas, você não deseja isso). Assim, você pode executar um dos seguintes procedimentos:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file
Steven D
fonte
6
Quando tentei tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; done, não funcionou. Ele trabalhou com um arquivo canalizado no whileloop, trabalhou apenas com tail -f, trabalhou com apenas grep, mas não com os dois tubos. Dando o grepa --line-bufferedopção fez trabalho
Isso funciona também quando cada linha precisa ser enviada para o stdin:command | while read -r line; do echo "$line" | command ; done
Den
21

Concordo com Keith, xargs é a ferramenta mais geral para o trabalho.

Eu costumo usar uma abordagem de 3 etapas.

  • faça as coisas básicas até ter algo com o qual gostaria de trabalhar
  • prepare a linha com o awk para obter a sintaxe correta
  • então deixe o xargs executá-lo, talvez com a ajuda do bash.

Existem maneiras menores e mais rápidas, mas essas formas quase sempre funcionam.

Um exemplo simples:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

as 2 primeiras linhas selecionam alguns arquivos para trabalhar e, em seguida, o awk prepara uma boa sequência com um comando a ser executado e alguns argumentos e $ 1 é a primeira entrada de coluna do canal. E, finalmente, garanto que o xargs envie essa string para o bash que apenas a executa.

É um pouco exagerado, mas esta receita me ajudou em muitos lugares, pois é muito flexível.

Johan
fonte
6
Note, xargs -0usa o byte nulo como um separador de registro, assim que sua declaração de impressão awk deve serprintf("MyJavaProg --args \"%s\"\0",$1)
glenn jackman
@glenn: Faltou o caractere nulo, irá atualizar a resposta
Johan
@Johan não um grande negócio, mas se você está usando awkvocê pode tê-lo fazer o jogo padrão e ignorar o grep exemplo,ls | awk '/xls/ {print...
Eric Renouf
15

O GNU Parallel é feito para esse tipo de tarefa. O uso mais simples é:

cat stuff | grep pattern | parallel java MyProg

Assista ao vídeo de introdução para saber mais: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
fonte
1
Não existe uma verdadeira necessidade para o cataqui desde greppode ler diretamente o arquivo
Eric Renouf
1
Obrigado pelo link, eu não necessariamente concordo que é mais fácil de ler, mas é bom saber que foi considerado independentemente. Gostaria apenas agora de reclamar levemente que o link realmente não se aplica aqui, já que a alternativa não é realmente, < stuff grep patternmas é grep pattern stuffsem redirecionamento ou gato necessário. Ainda assim, isso não materialmente alterar o seu argumento e se você acha que é mais claro para usar sempre as coisas de uma tubulação que começa com cat, em seguida, poder para você
Eric Renouf
8

Além disso, faça um while readloop na casca do peixe (presumo que você queira a casca do peixe, considerando que você usou a etiqueta do ).

command | while read line
    command $line
end

Alguns pontos a serem observados.

  • readnão aceita -rargumentos e não interpreta suas barras invertidas, para facilitar o caso de uso mais comum.
  • Você não precisa citar $line, pois ao contrário do bash, o peixe não separa variáveis ​​por espaços.
  • commandpor si só, é um erro de sintaxe (para capturar esse uso de argumentos de espaço reservado). Substitua-o pelo comando real.
Konrad Borowski
fonte
Não whileprecisa ser emparelhado com do& em donevez de end?
aff
@afff Trata-se especificamente de cascas de peixes, com sintaxe diferente.
21918 Konrad Borowski
Ah, então é isso que o peixe significa.
aff
6

Se você precisar controlar onde exatamente o argumento de entrada será inserido em sua linha de comandos ou se precisar repeti-lo várias vezes, precisará usar xargs -I{}.

EXEMPLO 1

Crie uma estrutura de pastas vazia another_folderque espelha as subpastas no diretório atual:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
EXEMPLO # 2

Aplique uma operação em uma lista de arquivos provenientes de stdin, neste caso, faça uma cópia de cada .htmlarquivo anexando uma .bakextensão:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

Na xargspágina de manual do MacOS / BSD :

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

xargsPágina de manual do Linux :

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.
ccpizza
fonte
1

Ao lidar com entradas potencialmente não autorizadas, eu gosto de ver todo o trabalho 'detalhado', linha por linha, para inspeção visual antes de executá-lo (especialmente quando é algo destrutivo, como limpar a caixa de correio das pessoas).

Então, o que eu faço é gerar uma lista de parâmetros (por exemplo, nomes de usuário), alimentá-lo com um arquivo de um registro por linha, assim:

johndoe  
jamessmith  
janebrown  

Em seguida, abro a lista vime a manuseio com expressões de pesquisa e substituição até obter uma lista de comandos completos que precisam ser executados, assim:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

Dessa forma, se o seu regex estiver incompleto, você verá em qual comando haverá problemas em potencial (ie. /bin/rm -fr johnnyo connor). Dessa forma, você pode desfazer seu regex e tentar novamente com uma versão mais confiável. A confusão de nomes é notória por isso, porque é difícil cuidar de todos os casos extremos, como Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Ter set hlsearché útil para fazer isso vim, pois destacará todas as correspondências, para que você possa identificar facilmente se não corresponder ou corresponder de forma não intencional.

Uma vez que seu regex é perfeito e captura todos os casos em que você pode testar / pensar, geralmente o converto em uma expressão sed para que possa ser totalmente automatizado para outra execução.

Nos casos em que o número de linhas de entrada o impede de fazer uma inspeção visual, recomendo repetir o comando na tela (ou, melhor ainda, em um log) antes de executar, portanto, se ocorrer um erro, você saberá exatamente qual comando causou falhar. Depois, você pode voltar ao seu regex original e ajustar novamente.

Marcin
fonte
0

Se um programa ignora o canal, mas aceita arquivos como argumentos, basta apontá-lo para o arquivo especial /dev/stdin.

Eu não estou familiarizado com java, mas aqui está um exemplo de como você faria isso para o bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

O $ é necessário para que o bash se traduza \nem novas linhas. Não sei por que.

Rolf
fonte
0

Eu prefiro isso - permitindo comandos de várias linhas e código claro

find -type f -name filenam-pattern* | while read -r F
do
  echo $F
  cat $F | grep 'some text'
done

ref https://stackoverflow.com/a/3891678/248616

Nam G VU
fonte
0

Aqui, um copypaste que você pode usar imediatamente:

cat list.txt | xargs -I{} command parameter {} parameter

O item da lista será colocado onde {} está e o restante do comando e parâmetros serão usados ​​como estão.

DustWolf
fonte