O TCL pode fazer esse tipo de coisa . bash
não é TCL; -)
Veja a solução zsh
pura de Stéphan também (já que zsh
tem coprocs simultâneos):
alias do='do coproc {' done='}; done'
Não é que não funcione em bash
, mas há dois problemas: bash
apenas suporta adequadamente um único coproc (veja abaixo); e você deve nomear seus coprocs quando tiver mais de um, e bash
atualmente não aplica expansão ao nome (ou seja, coproc $FOO
é um erro), ele aceita apenas um nome estático (solução alternativa é eval
).
Mas, com base nisso e presumindo que você não precisa exatamente de coproc
, o que você pode fazer é:
shopt -s expand_aliases # enable aliases in a script
alias do='do (' done=')& done' # "do (" ... body... ")& done"
e isso "promoverá" cada do
body para uma sub-base em segundo plano. Se você precisar sincronizar na parte inferior do loop, um wait
fará o truque ou será mais seletivo para scripts não triviais:
shopt -s expand_aliases
declare -a _pids
alias do='do (' done=')& _pids+=($!); done'
for i in $(seq 1 5);do sleep $[ 1 + $RANDOM % 5 ]; echo $i;done
wait ${_pids[*]}
Isso acompanha cada nova subshell na matriz _pids
.
(O uso de do { ... } &
também funcionaria, mas inicia um processo extra de shell.)
Por favor note provavelmente nenhum dos seguintes deve aparecer no código de produção!
Parte do que você quer pode ser feito (embora de forma deselegante e sem grande robustez), mas os principais problemas são:
-
Os coprocessos não são apenas para paralelização, eles são projetados para interação síncrona com outro processo (um efeito sendo stdin e stdout são alterados para pipes, e como um subshell, pode haver outras diferenças, por exemplo,
$$
) - mais importante, bash (até 4.4) apenas suporta um único coprocess de cada vez .
- você pode quebrar o controle de loop e efetivamente perder
break
eexit
se o corpo for executado em segundo plano (mas isso não afeta os exemplos simples aqui)
Você não pode obter o bash para fazer isso sem modificações não triviais em seus componentes internos (incluindo a implementação incompleta de co-processos simultâneos). Os comandos devem ser agrupados explicitamente com ( )
ou { }
, isso está implícito em construções de nível gramatical como for/do/done
. Você poderia pré-processar seus scripts bash, mas isso exigiria um analisador (o atual é ~ 6500 linhas de entrada C / bison) para ser robusto.
(O gentil leitor pode querer desviar o olhar agora.)
Você pode "redefinir" do
com um alias e uma função, potencialmente sujeitos a muitas complicações (escape, citações, expansão, etc.):
function _do()
{
( eval "$@" ) &
}
shopt -s expand_aliases # allow alias expansion in a script
alias do="do _do"
# single command only
for i in {1..5}; do sleep $((1+$RANDOM%5)); done
Mas, para passar vários comandos, você deve escapar e / ou citar:
for i in {1..5}; do "sleep $((1+$RANDOM%5)); echo $i" ; done
Nesse caso, você também pode dispensar o truque alias
e chamar o _do
diretamente ou apenas alterar os loops de qualquer maneira.
(O gentil leitor pode agora querer sair correndo do prédio.)
Aqui está uma versão ligeiramente menos propensa a erros usando uma matriz, mas isso requer modificações adicionais em seu código:
function _do()
{
local _cmd
printf -v _cmd "%s;" "$@"
( eval "$_cmd" ) &
}
cmd=( 'sleep $((1+$RANDOM%5))' )
cmd+=( 'echo $i' )
for i in {1..5}; do "${cmd[@]}" ; done
(Eu assumo agora que o gentil leitor desmaiou ou está protegido contra outros danos.)
Por fim, só porque isso pode ser feito, e não porque seja uma boa ideia :
function _for() {
local _cmd _line
printf -v _cmd '%s ' "$@"
printf -v _cmd "for %s\n" "$_cmd" # reconstruct "for ..."
read _line # "do"
_cmd+=$'do (\n'
while read _line; do
_cmd+="$_line"; _cmd+=$'\n' # reconstruct loop body
done
_cmd+=$') &\ndone' # finished
unalias for # see note below
eval "$_cmd"
alias for='_for <<-"done"'
}
shopt -s expand_aliases
alias for='_for <<-"done"'
for i in $(seq 1 5)
do
sleep $((1+$RANDOM%5))
echo $i
done
A única alteração necessária é que do
/ done
apareçam sozinhos em suas próprias linhas, e done
deve ser indentado com 0 ou mais divisórias sólidas.
O que isso faz é:
- hijack
for
com um alias que chama uma função com um HEREDOC que engole o corpo atédone
. - essa função reconstrói o loop
for
de seus argumentos e depois lê o resto do corpo a partir do HEREDOC - o corpo reconstruído tem "(...) &" inserido nele e
done
anexado - invoque
eval
, tendo desfeito ofor
alias (um prefixo\
funciona para comandos com alias , mas não para palavras-chave, é necessáriounalias
e restauração)
Mais uma vez, isso não é muito robusto: um redirecionamento do loop como done > /dev/null
causará problemas, assim como aninhado for
, assim como a aritmética for (( ;; ))
que precisa de cotação extra para funcionar. Você não pode usar o mesmo truque para sobrescrever do
, além de provavelmente quebrar select/do/done
, seria mais simples se funcionasse, mas você obteria um erro de sintaxe ( do
com um redirecionamento) se tentasse isso.