Existe uma maneira de especificar uma seção de um pipeline completamente “pass through”?

3

Eu tenho um script em que os dados são processados por streaming através de um pipeline bastante grande. Várias seções do pipeline são, na verdade, funções de "painel de controle" que fazem coisas diferentes com base em algum parâmetro externo. Um exemplo inventado é dado abaixo.

#! /bin/bash

switchboard() {
    # Select the appropriate command depending on input.
    case "$1" in
        1)
            sort
            ;;
        2)
            awk '{ print $5 }' | sort
            ;;
        *)
            cat  # <= Is there something more optimal here?
            ;;
    esac
}

# The data processing pipeline.
<"$1" tr '[:upper:]' '[:lower:]' | switchboard "$2" | head -n 10

Na função "switchboard", o fallback é apenas usar cat para enviar a entrada diretamente para a saída. Isso funciona muito bem, mas no meu pipeline eu posso ter muitos "painéis de controle" e gostaria de evitar a criação de um monte de processos do-nothing cat , se possível.

Existe algum tipo de bash built-in (ou alternativa) que pode ser usado para especificar que uma determinada seção de um pipeline deve conectar STDOUT diretamente ao STDIN sem ter que usar um subprocesso? (Eu tentei : , mas isso apenas comeu os dados) Ou, cat usa uma quantidade tão pequena de recursos que isso não é um problema?

    
por SethMMorton 18.09.2018 / 05:55

1 resposta

1

Primeiro, o uso de outro cat não faz muita diferença, e você não deve se preocupar com isso.

Em segundo lugar, os comandos que compõem um pipeline são executados em processos separados, independentemente de serem comandos externos ou internos:

$ a=0
$ a=1 | a=2 | a=3
$ echo $a
0

Quanto ao seu problema exato, não é possível simplesmente conectar 'stdin' a 'stdout'; mesmo se um shell tivesse algum nop embutido que entraria em colapso quando usado em um pipeline (por exemplo, | nop | - > | ), o shell não tem como saber de antemão, no momento em que configura o pipeline, que sua "central telefônica" mudará para nop em vez de awk ou sort .

Você também pode obter o mesmo efeito que "painéis de controle", construindo você mesmo o pipeline e chamando eval para executá-lo. Exemplo:

$ cat test.sh
type='file -zi "$1"'
case $type in
*application/gzip*)     mycat='zcat "$1"';;
*)                      mycat='cat "$1"';;
esac
case $type in
*charset=utf-16le*)     mycat="$mycat | iconv -f utf16le";;
esac
# highlight comments in blue
esc='printf "\x1b"';
mycat="$mycat | sed 's/^#.*/$esc[34m&$esc[m/'"
echo >&2 "$mycat"    # show the built pipeline
eval "$mycat"   # ... and run it
$ iconv -t utf16 test.sh > test16.sh; gzip test16.sh
$ sh test.sh test16.sh.gz

Isso é um pouco fora do tópico, mas no Linux existe uma maneira mais rápida de copiar o stdin para stdout (se algum deles é um pipe) - o splice (2) syscall, que não envolve mover os dados de e para a terra do usuário:

$ cat splice_cat.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <err.h>

int main(int ac, char **av){
    ssize_t r;
    size_t block = ac > 1 ? strtoul(av[1], 0, 0) : 0x20000;
    for(;;)
            if((r = splice(0, NULL, 1, NULL, block, 0)) < 1){
                    if(r < 0) err(1, "splice");
                    return 0;
            }
}
$ cc -Wall splice_cat.c -o splice_cat
$ dd if=/dev/zero bs=1M count=100 status=none | (time cat >/dev/null)
real    0m0.153s
user    0m0.012s
sys     0m0.056s
$ dd if=/dev/zero bs=1M count=100 status=none | (time ./splice_cat >/dev/null)
real    0m0.100s
user    0m0.004s
sys     0m0.020s

No entanto (afaik), isso não é usado nem pelo shell nem pelo cat , dd , etc.

    
por 18.09.2018 / 13:19

Tags