Eu quero contar o número de linhas em um tubo e depois continuar o tubo dependendo do resultado.
eu tentei
x=$(printf 'faa\nbor\nbaz\n' \
| tee /dev/stderr | wc -l) 2>&1 \
| if [[ $x -ge 2 ]]; then
grep a
else
grep b
fi
Mas não filtra (nem para "a" nem para "b"). Isso foi bastante inesperado, pois pelo menos estes funcionam conforme o esperado:
printf 'faa\nbor\nbaz\n' | if true; then grep a; else grep b; fi
printf 'faa\nbor\nbaz\n' | if false; then grep a; else grep b; fi
Parece que não consigo redirecionar o stderr de dentro da substituição do comando, pois isso também não funciona (no bash). Ele imprime todas as três linhas:
x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a
Em zsh, ele imprime apenas duas linhas.
Mas em ambos os shells a variável x não é definida após o pipeline e nem mesmo durante a segunda metade do pipeline.
O que posso fazer para contar as linhas em um pipeline e depois agir de acordo com esse número? Eu gostaria de evitar arquivos temporários.
Este comentário é verdadeiro:
Isso não significa que você não pode fazer nada. Um pipeline pode ser considerado o principal canal de dados, ainda os processos podem se comunicar usando canais laterais: arquivos, fifos nomeados ou qualquer outra coisa (embora às vezes você precise ter um cuidado extra e não deixá-los bloquear).
Você deseja contar o número de linhas e processar condicionalmente todo o fluxo de dados posteriormente. Isso significa que você precisa chegar ao final do fluxo e só então passar o fluxo inteiro. Então você precisa salvar todo o fluxo de alguma forma. Um arquivo temporário parece uma abordagem sensata. Você deve dividir seu tubo em pelo menos duas partes. A primeira parte deve salvar os dados em um arquivo; então as linhas devem ser contadas (essa tarefa pode pertencer à primeira parte, eu acho); em seguida, a última parte deve obter o número, ler o arquivo para receber os dados desde o início e agir de acordo.
Se você realmente deseja evitar arquivos temporários, alguma parte do seu pipeline deve agir de alguma forma como
sponge
. Para evitar canais laterais, o número de linhas deve ser passado como a primeira linha da saída e a parte restante do pipeline deve entender esse protocolo.Considere este comando:
Ele acumula linhas em um espaço de espera. Se houver pelo menos uma linha, depois de receber a última linha,
sed
imprime o número de linhas seguido por uma linha vazia e a entrada real.A linha vazia é desnecessária, mas aparece "naturalmente" neste código simples. Em vez de tentar evitá-lo em
sed
, eu simplesmente lidaria com isso mais tarde no pipe (por exemplo, comsed '2 d'
).Exemplo de uso:
Notas:
IFS= read -r
é um exagero porque a primeira linha é bem definida e contém um único número (ou não existe)./bin/sh
. O código também será executado no Bash.Você não pode assumir que
sed
é capaz de armazenar qualquer quantidade arbitrária de dados. A especificação POSIX diz:Portanto, pode ser que o limite seja de apenas 8192 bytes. Por outro lado, posso imaginar um arquivo temporário com 1 TB de dados facilmente. Talvez não evite arquivos temporários a todo custo.
O título diz "contar o número de linhas", mas seu exemplo tenta decidir se o número é 2 ou mais (N ou mais em geral). Esses problemas não são equivalentes. Após a 2ª (Nª) linha de entrada, você sabe a resposta para o último problema, até as linhas aparecerão indefinidamente. O código acima não pode lidar com entrada indefinida. Vamos corrigi-lo até certo ponto.
Este comando se comporta como a solução anterior, exceto que quando chega à 6ª linha assume (imprime) que o número de linhas é
6+
. Em seguida, as linhas já vistas são impressas e as linhas seguintes (se houver) são impressas assim que aparecem (cat
comportamento -like).Exemplo de uso:
Notas:
sed
(qualquer que seja a limitação no seu caso) ainda se aplica. Mas agorased
processa no máximo um$threshold
número de linhas; se$threshold
for baixo o suficiente, então deve estar OK.$threshold+
mas o protocolo permite distinguir entre 0, 1, 2, …, limite menos um e limite ou mais linhas.Não sou muito habilidoso em
sed
. Se meused
código pode ser simplificado, por favor, deixe-me uma dica em um comentário.Com base na discussão e no código sed do Kamil, encontrei esta solução awk: