Existe uma maneira de canalizar a saída de uma função para outra, mas somente se houver é uma saída?
Existe uma maneira de canalizar a saída de uma função para outra, mas somente se houver é uma saída?
Respostas:
A ifnotempty
função a seguir canaliza sua entrada para o comando passado como argumento, exceto que ela não faz nada se a entrada estiver vazia. Use-o para tubo source --foo
em sink --bar
por escrito source --foo | pipe_if_not_empty sink --bar
.
pipe_if_not_empty () {
head=$(dd bs=1 count=1 2>/dev/null; echo a)
head=${head%a}
if [ "x$head" != x"" ]; then
{ printf %s "$head"; cat; } | "$@"
fi
}
Notas de projeto:
dd
não ler mais do que o byte solicitado para ler em sua entrada padrão.head -c 1
que seria um substituto adequado para dd bs=1 count=1 2>/dev/null
no Linux.head -n 1
não seria adequado, porque head
normalmente armazena em buffer sua entrada e pode ler mais do que a linha que ela gera - e, como está lendo em um canal, os bytes extras são perdidos.read -r head
e até mesmo read -r -n 1 head
não são adequados aqui porque, se o primeiro caractere for uma nova linha, head
seria definido como a sequência vazia, impossibilitando a distinção entre entrada vazia e entrada iniciando com uma linha em branco.head=$(head -c 1)
porque, se o primeiro caractere for uma nova linha, a substituição do comando removeria a nova linha final, tornando impossível distinguir entre entrada vazia e entrada começando com uma linha em branco.cat
por </dev/stdin
um ganho de desempenho microscópico.Se você não se importa em armazenar todos os dados intermediários na memória, aqui está uma implementação muito mais simples pipe_if_not_empty
.
pipe_if_not_empty () {
input=$(cat; echo a);
if [ "x$input" != x"a" ]; then
{ printf %s "${input%a}"; } | "$@"
fi
}
Aqui está uma implementação um pouco mais simples com as seguintes advertências:
Novamente, todos os dados são armazenados na memória.
pipe_if_not_empty () {
input=$(cat);
if [ "x$input" != x"" ]; then
{ printf '%s\n' "${input}"; } | "$@"
fi
}
Isso deve funcionar para você
$ --a function-- | [ xargs -r ] --another function--
Um exemplo
$ echo -e "\n\n" | xargs -r ls
$ # No output. ls did not run.
$ echo -e "\n\n1" | xargs -r ls
ls: cannot access 1: No such file or directory
É simples, mas deve funcionar para você. Se a sua "uma função" envia uma string vazia ou até mesmo uma nova linha no pipeline, xargs -r, impedirá a passagem para "outra função".
Referência para xargs: http://www.oreillynet.com/linux/cmd/cmd.csp?path=x/xargs
-r, --no-run-if-empty
Do not run command if standard input contains only blanks.
A função abaixo tenta ler o 1º byte e, se for bem-sucedido, faz eco nesse byte e faz o resto dos gatos. Deve ser eficiente e 100% portátil.
if_read() {
IFS="" read -rN 1 BYTE && { echo -nE "$BYTE"; cat; } | "$@";
}
Casos de teste:
$ echo -n | if_read wc -c
$ echo | if_read wc -c
1
$ echo -en "\nX" | if_read wc -c
2
$
echo -en "\nX" | pipe_if_not_empty mail -s "Subject line here" foo@bar.com
. Ele pensa isso line
e here
é ambos os destinatários do e-mail, não os tokens no assunto. Eu tenho que escapar do "
assunto ao redor para fazê-lo funcionar. No entanto, a pipe_if_not_empty
função da resposta aceita funciona para mim mesmo sem escapar de nada.
Pelo menos algo assim funciona:
yourcommand | if [ $(wc -c) -gt "0" ]; then yourothercommand; fi
Observe que o item acima considerará os feeds de linha e outros caracteres especiais como saída, portanto, uma linha vazia passou para essa instrução if será considerada como saída. Apenas aumente o limite -gt se sua saída normalmente for superior a 1 byte :)
yourothercommand
nunca vê a saída de yourcommand
.
Em vez de sender | receiver
:
tester () { local a=$(</dev/stdin); if [[ $a ]]; then printf '%s\n' "$a" | receiver; fi; }
sender | tester
Ou você pode torná-lo mais geral, alterando-o para aceitar o programa receptor como argumento, como na resposta de Gilles:
tester () { local a=$(</dev/stdin); if [[ $a ]]; then printf '%s\n' "$a" | "$@"; fi; }
sender | tester receiver