Juntaram pipelines

5

Considerando uma rotina como esta:

alpha() { echo a b c |tr ' ' '\n'; }

que gera um fluxo, eu gostaria de pegar o fluxo de saída, transformá-lo e paste com o fluxo de saída original.

Se eu fizer uma atualização de exemplo, posso conseguir o que quero com:

$ mkfifo p1 p2
$ alpha | tee p1 >( tr a-z A-Z > p2) >/dev/null &
$ paste p1 p2
a       A
b       B
c       C

A minha pergunta é, existe uma maneira melhor de fazer isso, de preferência um que não envolva pipes nomeados?

    
por PSkocik 25.07.2015 / 02:06

1 resposta

6

Na maioria dos unices você pode referenciar um descritor de arquivo por seu link de dispositivo.

alpha(){ echo a b c | tr \  \n; }
alpha | { alpha |
    tr \[:lower:] \[:upper:] |
    paste - /dev/fd/9
}   9<&0
A   a
B   b
C   c

pipes nomeados

Ok, eu já tinha uma versão bem funcional disso. A versão aberta no meu editor (ainda não concluído) não está funcionando atualmente - eu meio que rasguei toda a opção analisando partes aos poucos e apenas me lembro vagamente da minha intenção. Mas isso ...

_p(){   : "$((_$$=0))"
    cd -P -- "${TMPDIR:-/tmp}"          &&
    while   [ -h "$$.$((_$$+=$$))" ]    ||
            [ -e "$$.$((_$$))" ]
    do :;   done                        &&
    mkfifo  -m a-w,u=rwx "$$.$((_$$))"  &&
    printf  %s "$PWD/$$.$((_$$))"       &&
    exec    >&- >&0                     &&
    case    $1 in
    (+)     shift
            eval "  rm $$.$((_$$))
                    cd - >/dev/null
            $@" < "$$.$((_$$))" &;;
    (-)     shift
            eval "  rm $$.$((_$$))
                    cd - >/dev/null
            $@" >   "$$.$((_$$))"   &;;
    (*) set --
        : "${1:?"USAGE: [-+] [cmd args...]"}"
    esac
}   <&- <>/dev/null

Você provavelmente notará que algumas coisas realmente estranhas estão acontecendo com $((_$$)) . É uma maneira meio hacky de tentar pisar no ambiente do comando o mínimo possível. Eu tento muito aqui manter todos possíveis valores de ambiente o mais puro possível, porque o objetivo dessa função é fazer o mesmo que a substituição de processos - executar um comando arbitrário que grava em um tubo.

cat "$(_p - echo a b c)"
a b c

Aqui está uma versão simplificada do fluxo de trabalho:

mkfifo pipe                         # make a pipe
printf %s\n "$PWD/pipe"            # substitute its file name
exec <&- </dev/null >&- >/dev/null  # break cmd sub i/o
eval "rm pipe;$@" >pipe &           # eval rm; args

Como o comando eval agrupa todos os seus argumentos em um único comando simples, o stdout de eval é o stdout do grupo de comando eval 'd inteiro. E é o shell que possui esse descritor de pipe e não qualquer um de seus processos filhos que meramente o herdam como stdout. E assim o shell faz o open() - e porque nós fazemos um bloqueio open() (como > ao invés de <> ) o shell trava até aquele tubo recebe um leitor.

Isso significa que rm não executa - não pode ser executado - até que algum outro processo estabeleça um descritor de arquivo de leitura para esse pipe. Quando algum processo abre para ler (como cat faz acima) o shell pára de ser suspenso, e a primeira ação tomada é que o canal é rm 'd - antes de mais nada. Isto não é um problema - o pipe tem um processo de leitura e um processo de gravação. Tudo está bem.

É um pouco buggy. Por exemplo:

echo "$(_p -)"
/tmp/6023.6023

^ Esse pipe não recebe rm 'd. echo não lê. Então, eval ainda está esperando. Para matá-lo:

: </tmp/6023.6023

... é o suficiente para matá-lo, no entanto.

Coisas como estas são as que eu uso para:

exec 9<> "$(_p -)"
sed -ue's/sed/mikeserv/' <&9 &
echo hi sed >&9
hi mikeserv

A propósito, mostrei apenas o <() equivalente, mas >() pode ser simulado como ...

echo hi sed >"$(_p + sed -e's/sed/mikeserv/' '>&2')"
hi mikeserv

outros canos

Você não precisa da minha função hacky para isso. A substituição de processos é bastante difundida. E não há nada que impeça você de manter seu cachimbo quando você pede um - você não precisa atribuí-lo imediatamente e descartá-lo.

(Nota paranóica: Eu odeio isso porque eu nunca sei de onde esses arquivos vêm - provavelmente você é um indivíduo mais bem ajustado e então o seguinte é aceitável para você)

eval "exec 9<> " <(:)

Tem um cachimbo - é todo seu. Não há limpeza necessária. Você é bom para ir.

_pp - hehe

_pp(){  :   "$((_$$=0))"
        _e()    case    ${1:-0} in
                (i|0)   exec >&- 1<>/dev/null;;
                (o|1) ! exec <&-  <>/dev/null;;
                (e|[2-9])
                        exec <&-  <>/dev/null >&- >&0
                        set     "${1#e}"
                        return  "${1:-2}";;
                (*[\<\>]*)
                        eval "
                        exec <&-  <>/dev/null >&- >&0 $1"
                esac
        cd -P   -- "${TMPDIR:-/tmp}"            &&
        while   [ -h "$$.$((_$$+=1))" ]         ||
                [ -e "$$.$((_$$))"    ]
        do :;   done                            && 
        mkfifo  -m a-w,u=rwx   "$$.$((_$$))"    &&
        printf  "%s/%s" "$PWD" "$$.$((_$$))"    &&
        case    $1  in
        (+*)    set \> \< "$@"  ;;
        (-*)    set \< \> "$@"  ;;
        (*)     rm  "$$.$((_$$))"
                set ''  USAGE:  '<-+>[+-[fd][file]]'
                ${1:?"$(printf "\n%s\t%s [cmd...]$@")"};;
        esac    &&
        case    $3  in
        (?|?[ioe0-9])
                _e  "${3#?}"
                eval "exec $1&$?";;
        (?/*)   _e "$1"'"$2"' "${3#?}";;
        (*)     _e "$1"'"$2"' "$OLDPWD/${3#?}"
        esac    &&
        eval "  shift 3
                eval \" rm $$.$((_$$))
                        cd - >/dev/null
        \$@\"   $2$$.$((_$$)) &"
}

... então ... eu finalmente consegui fazer _p im p funcionar. As opções [-+] podem ter qualquer uma das ioe opções para significar std(in|out|err) (embora stdout seja bastante inútil no comando sub) ou [0-9] para referenciar quaisquer descritores que você já tenha configurado, ou qualquer outra coisa a ser interpretada como qualquer nome de arquivo arbitrário. Quando chamado com + , o argumento é entendido como um fluxo de saída - como a entrada deve vir do comando com o qual você o chama - e com - o argumento é usado como entrada.

E assim, com isso, o último sed acima pode ser escrito como:

echo hi sed >"$(_pp +2 sed s/sed/mikeserv/)"
hi mikeserv
    
por 25.07.2015 / 02:51