Tornar um tubo condicional ao retorno não vazio

Respostas:

9

A ifnotemptyfunçã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 --fooem sink --barpor 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:

  • Eu esperaria que essa implementação funcionasse em todas as plataformas POSIX / Unix, embora, estritamente falando, não seja compatível com os padrões: ela depende de ddnão ler mais do que o byte solicitado para ler em sua entrada padrão.
  • Eu acho head -c 1que seria um substituto adequado para dd bs=1 count=1 2>/dev/nullno Linux.
  • Por outro lado, head -n 1 não seria adequado, porque headnormalmente 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, headseria definido como a sequência vazia, impossibilitando a distinção entre entrada vazia e entrada iniciando com uma linha em branco.
  • Não podemos simplesmente escrever 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.
  • No bash, ksh ou zsh, você pode substituir catpor </dev/stdinum 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:

  • Os dados produzidos pela fonte são considerados vazios se e somente se consistirem apenas em caracteres de nova linha. (Isso pode ser de fato desejável.)
  • Os dados alimentados no coletor terminam com exatamente um caractere de nova linha, independentemente de quantas novas linhas os dados produzidos pela fonte terminam. (Isso pode ser um problema.)

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
}
Gilles 'SO- parar de ser mau'
fonte
17

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.
Randy Skretka
fonte
9

ifne (1) da moreutils faz exatamente isso. Moreutils está disponível como um pacote pelo menos no Debian e Ubuntu, provavelmente em outras distribuições.

Jukka Matilainen
fonte
4

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
$
Benjamin Polak
fonte
Esta função falha para mim quando executo echo -en "\nX" | pipe_if_not_empty mail -s "Subject line here" [email protected]. Ele pensa isso linee 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_emptyfunção da resposta aceita funciona para mim mesmo sem escapar de nada.
precisa saber é o seguinte
2

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 :)

Janne Pikkarainen
fonte
yourothercommandnunca vê a saída de yourcommand.
Pausado até novo aviso.
2

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
Pausado até novo aviso.
fonte