xargs com redirecionamento stdin / stdout

20

Gostaria de executar:

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

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

Claro, isso pode ser feito por bash / python / qualquer script, mas eu gosto de escrever uma linha sexy. Tudo o que consegui alcançar é (ainda sem stdout):

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

Mas -a no xargs não entende substituir-str 'arquivo'.

Obrigado pela ajuda.

    
por Nikolay Vyahhi 07.10.2011 / 10:21

6 respostas

28

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

1. Maneira simples: para loop

Se você quiser processar apenas os arquivos em A/ , um simples loop for deve ser suficiente:

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

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

Veja um exemplo de como as coisas podem se tornar ruins se você usar ls com xargs para o trabalho. Considere o seguinte cenário:

  • 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
    

Você acabou de substituir os mypreciousfile.dat e mypreciousfile.dat.ans . Se houvesse algum conteúdo nesses arquivos, teria sido apagado.

2. Usando xargs : o caminho correto com find

Se você quiser insistir em usar xargs , use -0 (nomes com terminação nula):

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.ans ending; 2: este irá quebrar se algum nome de arquivo contiver um sinal de cotação ( " ). Ambos os problemas podem ser resolvidos de maneira diferente da invocação do shell:

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

3. Tudo feito dentro de find ... -exec

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

Isso, novamente, produz arquivos .dat.ans e será interrompido se os nomes dos arquivos contiverem " . Para fazer isso, use bash e altere a maneira como ele é invocado:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans'" {} \;
    
por 07.10.2011 / 10:50
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

    
por 07.10.2011 / 10:47
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, você precisa de outro utilitário para listar os arquivos (zsh ou bash 4 possuem padrões poderosos o suficiente para serem encontrados raramente, mas se você quiser ficar dentro do shell POSIX ou usar shell rápido como dash, precisará encontrar algo 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 irá lidar com novas linhas e não irá lidar com barras invertidas, porque por padrão ele interpreta sequências de escape (que na verdade permitem que você passe em uma nova linha, mas não consegue gerar esse formato). Muitas shells têm a opção -0 para serem lidas naqueles que você pode manipular todos os personagens, mas infelizmente não é POSIX.

    
por 07.10.2011 / 14:20
2

Use o Paralelo GNU:

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

Bônus adicionado: você obtém o processamento feito em paralelo.

Assista aos vídeos de introdução para saber mais: link

    
por 10.10.2011 / 15:34
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"

Edit: Deve-se notar que esta abordagem não funciona quando os nomes dos arquivos contêm espaço em branco. Não é possível trabalhar. Mesmo se você usou find -0 e xargs -0 para fazer com que os xargs entendam os espaços corretamente, a chamada -c shell iria interferir neles. No entanto, o OP solicitou explicitamente uma solução xargs, e esta é a melhor solução xargs que eu criei. Se o espaço em branco em nomes de arquivos puder ser um problema, use find -exec ou um loop de shell.

    
por 07.10.2011 / 10:58
1

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

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

o bit ${file%.dat}.ans removerá o sufixo .dat do nome do arquivo em $file e adicionará .ans ao final.

    
por 07.10.2011 / 10:52