Dang. Eu escrevi toda essa grande resposta com um método elaborado de analisar um arquivo tar
- foi legal. Mas cheguei ao fim e percebi que nada disso era necessário. Tudo que você precisa é sed
e um pouco de matemática:
set ./file[1-5];i=1 n=;eval "${n:=
} sed -n \"$(grep -c '.\|' "$@"|
sed 's|\(.*\):\(.*\)|\
$i,$(((/5)+(i+=)-))w |
')\" <<!$n"'$(cat "$@")'"$n!$n"
Há grep -c
conta linhas em quaisquer arquivos que você tenha globbed - eu globbed file[1-5]
- e entrega a contagem para sed
, que então - com uma pequena ajuda do shell - grava seu próprio script. cat
fornece a entrada via here-document. Isto é porque eu sou duvidoso sobre o que pode acontecer se sed
abrir e começar a escrever para um dos arquivos cat
está tentando ler para ele - também eu suspeito que seria um pouco melhor no manuseio buffers do que um pipe seria dependendo do tamanho - mas eu não sou muito claro sobre essa parte.
Assim, todos os arquivos são lidos em um único fluxo e w
processa a saída de acordo. É necessária uma pequena configuração para incrementar os números dos arquivos corretamente - daí grep
e eval
- nada terrível. Aqui está uma saída de set -x
para mostrar o que está fazendo:
+ set ./file1 ./file2 ./file3 ./file4 ./file5
+ i=1 n=
+ + grep -c .\| ./file1 ./file2 ./file3 ./file4 ./file5
sed s|\(.*\):\(.*\)|\
$i,$(((/5)+(i+=)-))w |
+ eval
sed -n "
$i,$(((18400/5)+(i+=18400)-18400))w ./file1
$i,$(((18411/5)+(i+=18411)-18411))w ./file2
$i,$(((18415/5)+(i+=18415)-18415))w ./file3
$i,$(((18418/5)+(i+=18418)-18418))w ./file4
$i,$(((18421/5)+(i+=18421)-18421))w ./file5" <<!
$(cat "$@")
!
+ cat ./file1 ./file2 ./file3 ./file4 ./file5
+ sed -n
1,3681w ./file1
18401,22083w ./file2
36812,40495w ./file3
55227,58910w ./file4
73645,77329w ./file5
Como você pode ver, as linhas são endereçadas com base na posição de cada arquivo no fluxo e são w
ritten conforme são lidas em seus respectivos nomes de arquivos. Importante, porém, isso não faz nenhuma tentativa de manipular nenhum caractere não-portátil em um nome de caminho - em particular, novas linhas em nomes de caminho não são iniciais neste caso, pois o comando sed
w
rite delimita argumentos de nome de arquivo em novas linhas. A situação é facilmente contornada, se necessário, com ln
, se você precisar.
Também devo mencionar que há um limite para o número de w
rite descritores de arquivos sed
podem suportar em um único script. A especificação diz :
[sed
is required] to support at least ten distinct w
files, matching historical practice on many implementations. Implementations are encouraged to support more, but conforming applications should not exceed this limit.
Portanto, o comando como descrito acima deve ser portátil para qualquer sistema POSIX para até 10 arquivos de leitura / gravação simultâneos. Se esse tipo de coisa fosse incorporado em um script ou aplicativo publicado em que mais poderia ser desejado, valeria a pena executar algumas verificações antes de processar dados reais em /tmp
. Como:
: & set '"" "" "" "" "" "" "" "" "" "" ';n='
' f=/tmp/$$$!'_$((i+=1))' MAXw=[num]
while eval "set '$1$1' $1;exec <<!$n\$(((i=0)+\$#))$n!$n
i=\$(sed \"$(IFS=\ ;printf "\nw $f%.0s" $1)\")"
[ "$(($#==i?(_i=i-1):(MAXw=_i)))" -lt "$MAXw" ]
do :;done; rm "/tmp/$$$!"*; unset _i i f n
... o qual deve razoavelmente portar a capacidade de sed
nessa área. O GNU sed
parou em 4093 arquivos w
abertos simultaneamente para mim em cerca de um segundo, mas isso é provavelmente o máximo do meu sistema, e pode ser afetado com ulimit
também. Quando acabou - porque a verificação dobra o valor de $i
para cada tentativa - $_i
foi deixado em 2560 e $i
em 5120. Eu padrão para definir $MAXw
para o mais seguro $_i
acima em loop close - principalmente porque não tenho certeza se todos os sed
s irão definir corretamente o seu retorno se eles não puderem abrir um arquivo w
- mas o leitor pode fazer com o que eles quiserem.
Observe que o valor [num]
inicial para $MAXw
deve ser um número real - seja qual for o número máximo desejado de arquivos w
- e não literalmente [num]
.
Sobre o documento aqui novamente - eu o considero - ou algo parecido - uma boa ideia neste caso. sed
deve manter seus descritores de gravação enquanto lê e então o que poderia fazer com nomes idênticos de entrada / saída que eu não conheço - mas eu não acho que seja uma chance que vale a pena quando as alternativas estão prontamente disponíveis para nós. / p>
Meus arquivos de teste foram gerados como:
for n in 1 2 3 4 5
do : & seq -s "$(printf "%015s--$n--%015s\n\t")" "$!" >"file$n"
done
... que obtém números pseudo-aleatórios razoavelmente sequenciais do kernel em PIDs de processo abandonados. O conteúdo do arquivo foi propositadamente projetado para indicar uma incompatibilidade na divisão. Veja como é um conjunto de amostras antes e depois:
Antes:
for f in file[1-5]; do
nl -ba "$f" | sed -n '$p;$=;1,3p
'; done
1 1 --1--
2 2 --1--
3 3 --1--
3681 3681 --1--
3681
1 1 --2--
2 2 --2--
3 3 --2--
3683 3683 --2--
3683
1 1 --3--
2 2 --3--
3 3 --3--
3684 3684 --3--
3684
1 1 --4--
2 2 --4--
3 3 --4--
3684 3684 --4--
3684
1 1 --5--
2 2 --5--
3 3 --5--
3685 3685 --5--
3685
Se a formatação parecer um pouco funky, provavelmente é porque seq
não insere a string -s
eparator antes da primeira linha de saída. O importante é que sed
, seq
e nl
pareçam concordar com os números de linha. Enfim ...
Depois:
...
sed -n
1,737w ./file1
3682,4418w ./file2
7365,8101w ./file3
11049,11785w ./file4
14733,15470w ./file5
...
1 1 --1--
2 2 --1--
3 3 --1--
737 737 --1--
737
1 1 --2--
2 2 --2--
3 3 --2--
737 737 --2--
737
1 1 --3--
2 2 --3--
3 3 --3--
737 737 --3--
737
1 1 --4--
2 2 --4--
3 3 --4--
737 737 --4--
737
1 1 --5--
2 2 --5--
3 3 --5--
738 738 --5--
738
E isso é simples, eficiente e transmitido.