xargs com redirecionamento stdin / stdout

21

Eu gostaria de correr:

./a.out < x.dat > x.ans

para cada * .dat arquivo no diretório A .

Claro, isso poderia ser feito pelo script bash / python / qualquer que seja o tipo de script, mas eu gosto de escrever uma frase sexy. Tudo o que pude alcançar é (ainda sem nenhum stdout):

ls A/*.dat | xargs -I file -a file ./a.out

Mas -a no xargs não entende o arquivo de substituição de str.

Obrigado pela ajuda.

Nikolay Vyahhi
fonte
Além das outras boas respostas aqui, você pode usar a opção -a do GNU xargs.
James Youngman

Respostas:

30

Primeiro de tudo, não use a lssaída como uma lista de arquivos . Use expansão de shell ou find. Veja abaixo as possíveis consequências do uso indevido de ls + xargs e um exemplo de xargsuso adequado .

1. Maneira simples: para loop

Se você deseja processar apenas os arquivos abaixo A/, um forloop simples deve ser suficiente:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 Por que não   ls | xargs ?

Aqui está um exemplo de quão ruim as coisas podem virar se você usar lscom xargspara o trabalho. Considere um cenário a seguir:

  • primeiro, vamos criar alguns arquivos vazios:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • veja os arquivos e eles não contêm nada:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • execute um comando mágico usando xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • o resultado:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Então, você acabou de substituir ambos mypreciousfile.date mypreciousfile.dat.ans. Se houvesse algum conteúdo nesses arquivos, ele teria sido apagado.


2. Usando   xargs : da maneira correta com  find 

Se você quiser insistir em usar xargs, use -0(nomes terminados em nulo):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Observe duas coisas:

  1. Dessa forma, você criará arquivos com .dat.ansfinal;
  2. isso será interrompido se algum nome de arquivo contiver um sinal de aspas ( ").

Ambos os problemas podem ser resolvidos por diferentes formas de invocação de shell:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Tudo feito dentro find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Isso, novamente, produz .dat.ansarquivos e será interrompido se os nomes dos arquivos contiverem ". Para fazer isso, use bashe altere a maneira como é chamado:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
fonte
+1 por mencionar para não analisar a saída de ls.
rahmu
2
A opção 2 é interrompida quando o nome dos arquivos contém ".
thiton
2
Muito bom ponto, obrigado! Vou atualizar em conformidade.
rozcietrzewiacz
Quero apenas mencionar que, se zshfor usado como shell (e SH_WORD_SPLIT não estiver definido), todos os casos especiais desagradáveis ​​(espaços em branco, "no nome do arquivo etc.) não precisam ser considerados. O trivial for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donefunciona em todos os casos.
jofel
-1 porque estou tentando descobrir como fazer xargs com stdin e minha pergunta não tem nada a ver com arquivos ou find.
Mehrdad
2

Tente fazer algo assim (a sintaxe pode variar um pouco, dependendo do shell que você usa):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done

rahmu
fonte
Isso não funcionaria. Ele tentaria operar coisas como somefile.dat.date redirecionar toda a saída para um único arquivo.
rozcietrzewiacz
Você está certo. Eu editei a solução para corrigi-lo.
rahmu
OK - Quase bom :) Apenas somefile.dat.anso material de saída não seria tão bom.
rozcietrzewiacz
1
Editado! Eu não sabia sobre '%'. Funciona como um encanto, obrigado pela dica.
Rahmu
1
Adicionar um -type fileseria bom (não pode < directory), e isso torna a fada incomum do nome do arquivo triste.
Mat
2

Para padrões simples, o loop for é apropriado:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Para casos mais complexos em que você precisa de outro utilitário para listar os arquivos (zsh ou bash 4 têm padrões suficientemente poderosos que você raramente precisa encontrar, mas se você deseja permanecer no shell POSIX ou usar o shell rápido como o traço, precisará encontrar qualquer coisa não trivial), enquanto a leitura é mais apropriada:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Isso manipulará espaços, porque a leitura é (por padrão) orientada a linhas. Ele não manipula novas linhas e não controla barras invertidas, porque, por padrão, interpreta sequências de escape (que na verdade permitem passar uma nova linha, mas a localização não pode gerar esse formato). Muitos shells têm a -0opção de ler, para que você possa lidar com todos os caracteres, mas infelizmente não é POSIX.

Jan Hudec
fonte
2

Use o GNU Parallel:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Bônus adicional: você realiza o processamento em paralelo.

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

Ole Tange
fonte
1

Eu acho que você precisa de pelo menos uma invocação de shell no xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Editar: Note-se que essa abordagem não funciona quando os nomes de arquivos contêm espaços em branco. Não pode trabalhar. Mesmo se você usasse find -0 e xargs -0 para fazer com que o xargs entendesse os espaços corretamente, a chamada do shell -c seria cortada por eles. No entanto, o OP pediu explicitamente uma solução xargs, e essa é a melhor solução xargs que eu criei. Se o espaço em branco nos nomes de arquivos for um problema, use find -exec ou um loop de shell.

thiton
fonte
@rozcietrzewiacz: Em geral, claro, mas presumo que alguém tentando fazer xargs vodu sabe disso. Como esses nomes de arquivos passam por outra expansão de shell, eles precisam ser bem comportados de qualquer maneira.
thiton
1
Você está errado sobre a expansão do shell. lsgera coisas como espaços sem escapar e esse é o problema.
rozcietrzewiacz
@rozcietrzewiacz: Sim, eu entendo esse problema. Agora, suponha que você escape adequadamente desses espaços e os coloque no xargs: eles são substituídos na string -c de sh, sh tokeniza a string -c e tudo quebra.
Thoff
Sim. Você vê agora, analisar lsnão é bom. Mas xargs pode ser usado com segurança com o find - veja a sugestão 2 na minha resposta.
rozcietrzewiacz
1

Não há necessidade de complicar. Você poderia fazer isso com um forloop:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

o ${file%.dat}.ansbit removerá o .datsufixo do nome do arquivo $filee, em vez disso, adicionará.ans ao final.

Kusalananda
fonte