Executar um comando uma vez por linha de entrada canalizada?

145

Eu quero executar um comando java uma vez para cada correspondência de ls | grep pattern - . Neste caso, acho que eu 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 peixe ou bash.)

    
por Xodarap 17.02.2011 / 05:26

10 respostas

87

Isso é o que o xargs faz.

... | xargs command
    
por 17.02.2011 / 05:58
149

A resposta aceita tem a ideia certa, mas a chave é passar xargs a opção -n1 , que significa "Execute o comando uma vez por linha de saída:"

cat file... | xargs -n1 command

Ou, para um único arquivo de entrada, você pode evitar o pipe a partir de cat e seguir:

<file xargs -n1 command
    
por 18.11.2014 / 16:44
106

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

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

read -r lê uma única linha da entrada padrão ( read sem -r interpreta barras invertidas, você não quer isso). Assim, você pode fazer o seguinte:

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

$ while read -r line; do command "$line"; done <file
    
por 17.02.2011 / 06:11
19

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

Eu costumo usar uma abordagem de 3 passos.

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

Existem maneiras menores e mais rápidas, mas isso quase sempre funciona.

Um exemplo simples:

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

as duas primeiras linhas selecionam alguns arquivos para trabalhar, então awk prepara uma string legal com um comando para executar e alguns argumentos e $ 1 é a primeira entrada de coluna do pipe. E finalmente eu me certifico de que o xargs envie essa string para o bash que acabou de executá-lo.

É um pouco exagerado, mas essa receita me ajudou em muitos lugares, já que é muito flexível.

    
por 17.02.2011 / 12:49
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: link

    
por 22.02.2011 / 14:55
8

Além disso, while read loop no fish shell (eu suponho que você queira shell de peixe, considerando que você usou tag).

command | while read line
    command $line
end

Poucos pontos a serem observados.

  • read não aceita o argumento -r , e ele não interpreta suas barras invertidas, para facilitar o uso do caso de uso mais comum.
  • Você não precisa citar $line , pois, diferentemente do bash, o peixe não separa as variáveis por espaços.
  • command por si só é um erro de sintaxe (para capturar esse uso de argumentos de espaço reservado). Substitua-o pelo comando real.
por 29.08.2013 / 13:53
6

Se você precisa controlar exatamente onde o argumento de entrada é inserido em sua linha de comando ou se você precisa repeti-lo várias vezes, então você precisa usar xargs -I{} .

EXEMPLO # 1

Crie uma estrutura de pastas vazia em another_folder que espelhe as subpastas no diretório atual:

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

Aplique uma operação em uma lista de arquivos proveniente de stdin, nesse caso, faça uma cópia de cada arquivo .html anexando uma extensão .bak :

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

Na página xargs man 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.

Página xargs man 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.
    
por 01.04.2018 / 10:56
1

Ao lidar com insumos potencialmente não-autorizados, eu gosto de ver todo o trabalho 'soletrado' 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 (ou seja, nomes de usuários), alimentá-los em um arquivo em um registro por linha, assim:

johndoe  
jamessmith  
janebrown  

Então eu abro a lista em vim , e mangle-o com pesquisa e substituo expressões até obter uma lista de comandos completos que precisam ser executados, assim:

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

Desta forma, se sua regex estiver incompleta, você verá em qual comando haverá problemas potenciais (por exemplo, /bin/rm -fr johnnyo connor ). Dessa forma, você pode desfazer sua regex e tentar novamente com uma versão mais confiável dela. Atrapalhar o nome é notório por isso, porque é difícil cuidar de todos os casos como Van Gogh, O'Connors, St. Clair e Smith-Wesson.

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

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

Para casos em que o número de linhas de entrada o impede de fazer uma inspeção visual, recomendo enfaticamente que o comando seja exibido na tela (ou melhor ainda, um log) antes de ser executado, portanto, se ocorrer erros, você saberá exatamente qual comando causou a falha. Então você pode voltar para sua regex original e ajustar mais uma vez.

    
por 19.02.2011 / 18:59
0

Se um programa ignorar o pipe, mas aceitar arquivos como argumentos, você poderá simplesmente apontá-lo para o arquivo especial /dev/stdin .

Eu não estou familiarizado com o 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 o bash converter \n em novas linhas. Não sei porquê.

    
por 05.05.2017 / 03:10
0

Eu prefiro isso - permitindo comandos com 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 link

    
por 14.08.2018 / 16:50

Tags