Piping o conteúdo de vários arquivos entre programas enquanto mantém o conteúdo separado

1

Eu gostaria de ler em vários arquivos e canalizar sua saída para programas subseqüentes, mantendo-os como pipelines individuais de dados.

program1 *.txt | program2 | program3 folder

Eu sei o que a sintaxe acima pode realizar para fluxos únicos de dados, mas estou procurando manter os arquivos separados durante toda a operação. O texto acima seria traduzido para o seguinte:

  1. programa1 lê arquivos de texto e canais para o programa2
  2. program2 processa dados individualmente e canaliza para o programa3
  3. program3 grava dados em arquivos na pasta com os mesmos nomes de arquivos que o original

Este tipo de operação é atualmente o domínio para ferramentas de construção como o Gulp, mas estou tentando ver se um shell pode substituí-las completamente. Como os programas são escritos para manipular apenas um stdin , isso não parece viável.

A leitura e a gravação de vários arquivos não são um problema, pois só lidei com isso dentro dos próprios programas.

Eu observei o seguinte, mas eles não parecem a solução correta:

  • o comando tee
  • descritores de arquivo
  • substituições

Uma maneira possível é criar um processo para cada arquivo individual e manter uma lista de nomes de arquivos em algum lugar, mas espero algo mais elegante.

    
por Dale 26.07.2015 / 04:46

2 respostas

1

Um pipe, assim como qualquer arquivo, é um fluxo de texto (mais precisamente, um fluxo de bytes). Os blocos básicos de construção do Unix tendem a ser simples. As interações entre processos são baseadas principalmente em dados não estruturados. O sistema operacional não fornece um canal de comunicação com vários fluxos rotulados por um nome de arquivo. Se os programas precisarem disso, eles precisarão organizar seus próprios - e tubos separados, um para cada fluxo, seriam a implementação mais natural.

Se program2 e program3 agirem independentemente em cada fluxo, execute uma cópia deles para cada um dos arquivos. Para executá-los sequencialmente, use um loop de shell. Como o pipe, o loop é um dos recursos do shell para unir programas. Para informar a program3 onde colocar a saída, a interface usual é para program3 gravar em sua saída padrão e usar uma construção de redirecionamento no shell para direcionar a saída para um arquivo. O shell fornece algumas construções básicas de manipulação de strings para construir nomes de arquivos; aqui é apenas concatenação.

for x in *.txt; do
  program1 "$x" | program2 | program3 >"folder/$x"
done

Se os programas são leves no IO, mas exigem muita CPU e você tem várias CPUs, você pode querer executá-las em paralelo. Com ferramentas GNU bastante recentes, você pode usar xargs para executar programas em paralelo. Passe o número de CPU no seu sistema como o argumento para -P . Como o comando que o xargs precisa executar é um pipeline, você precisa invocar um shell.

find -maxdepth 1 -name '*.txt' -print0 |
xargs -0 -n 1 -P 4 sh -c 'program1 "$1" | program2 | program3 >"$0/$1"' "folder"

Você pode usar GNU paralelo ao invés de xargs para determinar o número de CPUs no seu sistema automaticamente.

parallel sh -c 'program1 "$1" | program2 | program3 >"$0/$1"' "folder" ::: *.txt

Se você precisar de uma única instância de program2 e program3 para processar vários arquivos, será necessário criar esses programas com uma interface personalizada para receber vários canais como entrada. Não há maneira padrão de fazer isso. Um método é permitir que eles invoquem o programa que fornece sua entrada. Seria semelhante à maneira como xargs e parallel são informados sobre qual programa invocar para processar sua saída.

    
por 26.07.2015 / 14:51
1

Você está falando sobre

program1 file1.txt   | program2 | program3 > folder/file1.txt
program1 file2.txt   | program2 | program3 > folder/file2.txt
program1 file42.txt  | program2 | program3 > folder/file42.txt
program1 green.txt   | program2 | program3 > folder/green.txt
program1 indigo.txt  | program2 | program3 > folder/indigo.txt
program1 leopard.txt | program2 | program3 > folder/leopard.txt
program1 lion.txt    | program2 | program3 > folder/lion.txt
   ⋮        ⋮            ⋮          ⋮                 ⋮ 

?

Você pode fazer isso com

for f in file1.txt file2.txt file42.txt green.txt indigo.txt leopard.txt lion.txt ...
do
    program1 "$f" | program2 | program3 > folder/"$f"
done

Se você quiser fazer isso com todos os arquivos de texto no diretório atual, é só usar o curinga (a.k.a. "glob"):

for f in *.txt
do
    program1 "$f" | program2 | program3 > folder/"$f"
done
    
por 26.07.2015 / 07:44