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 já 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