As variáveis de ambiente não são configuradas quando minha função é chamada em um pipeline

9

Eu tenho a seguinte função recursiva para definir variáveis de ambiente:

function par_set {
  PAR=$1
  VAL=$2
  if [ "" != "$1" ]
  then
    export ${PAR}=${VAL}
    echo ${PAR}=${VAL}
    shift
    shift
    par_set $*
  fi
}

Se eu chamá-lo por si só, ele define a variável e ecoa para stdout:

$ par_set FN WORKS
FN=WORKS
$ echo "FN = "$FN
FN = WORKS

Redirecionar stdout para um arquivo também funciona:

$ par_set REDIR WORKS > out
cat out
REDIR=WORKS
$ echo "REDIR = "$REDIR
REDIR = WORKS

Mas, se eu canalizar stdout para outro comando, a variável não será definida:

$ par_set PIPE FAILS |sed -e's/FAILS/BARFS/'
PIPE=BARFS
$ echo "PIPE = "$PIPE
PIPE =

Por que o pipe impede que a função exporte a variável? Existe uma maneira de corrigir isso sem recorrer a arquivos temporários ou pipes nomeados?

Resolvido:

Código de trabalho graças a Gilles:

par_set $(echo $*|tr '=' ' ') > >(sed -e's/^/  /' >> ${LOG})

Isso permite que o script seja chamado assim:

$ . ./script.sh PROCESS_SUB ROCKS PIPELINES=NOGOOD
$ echo $PROCESS_SUB
ROCKS
$ echo $PIPELINES
NOGOOD
$ cat log
7:20140606155622162731431:script.sh:29581:Parse Command Line parameters.  Params must be in matched pairs separated by one or more '=' or ' '.
  PROCESS_SUB=ROCKS
  PIPELINES=NOGOOD

Projeto hospedado no bitbucket link se estiver interessado em código completo.

    
por Andrew 05.06.2014 / 21:38

3 respostas

7

Cada parte de um pipeline (ou seja, cada lado do pipe) é executada em um processo separado (chamado de subshell, quando um shell garfos um subprocesso para executar parte do script). Em par_set PIPE FAILS |sed -e's/FAILS/BARFS/' , a variável PIPE é configurada no subprocesso que executa o lado esquerdo do canal. Essa alteração não é refletida no processo pai (as variáveis de ambiente não são transferidas entre processos, elas são herdadas apenas por subprocessos.

O lado esquerdo de um tubo sempre é executado em uma subcamada. Algumas shells (ATT ksh, zsh) executam o lado direito nas shells pai; a maioria também executa o lado direito em um subnível.

Se você quer redirecionar a saída de uma parte do script e executar essa parte no shell pai, em ksh / bash / zsh, você pode usar substituição de processos .

par_set PROCESS SUBSTITUTION > >(sed s/ION/ED/)

Com qualquer shell POSIX, você pode redirecionar a saída para um pipe nomeado.

mkfifo f
<f grep NAMED= &
par_set NAMED PIPE >f

Ah, e você está perdendo aspas em torno de substituições de variáveis , seu código quebra em coisas como par_set name 'value with spaces' star '*' .

export "${PAR}=${VAL}"
…
par_set "$@"
    
por 06.06.2014 / 03:30
3

Isso não funciona porque cada lado do pipe é executado em um subshell em bash , e as variáveis definidas em um subshell são locais para esse subshell.

Atualização:

Parece fácil passar variáveis do pai para o shell filho, mas realmente é difícil fazer o contrário. Algumas soluções alternativas são pipes nomeados, arquivos temporários, gravação no stdout e leitura no pai, etc.

Algumas referências:

link
link
link
link

    
por 05.06.2014 / 21:44
0

Você aponta as subshells - que podem ser contornadas com alguns recursos no shell fora de um pipeline -, mas a parte mais difícil do problema tem a ver com a concorrência do pipeline .

Todos os membros do processo do pipeline começam em uma vez , e assim o problema pode ser mais fácil de entender se você analisar da seguinte forma:

{ sleep 1 ; echo $((f=1+2)) >&2 ; } | echo $((f))
###OUTPUT
0
...
3

Os processos de pipeline não podem herdar os valores das variáveis porque já estão desativados e em execução antes de a variável ser definida.

Eu realmente não consigo entender qual é o objetivo da sua função - qual é a finalidade de que export não atenda? Ou mesmo apenas var=val ? Por exemplo, aqui está quase o mesmo pipeline novamente:

pipeline() { 
    { sleep 1
      echo "f=$((f=f+1+2))" >&3
    } | echo $((f)) >&2
} 3>&1

f=4 pipeline

###OUTPUT

4
...
f=7

E com export :

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Então, sua coisa pode funcionar como:

par_set $(echo PIPE FAILS | 
    sed 's/FAIL/WORK/;/./w /path/to/log')

Qual seria o log de um arquivo de saída sed e entregá-lo à sua função como um "$@" split-shell.

Ou, alternativamente:

$ export $(echo PIPE FAILS | sed 's/ FAIL/=WORK/')
$ par_set $PIPE TWICE_REMOVED
$ echo "WORKS = "$WORKS
WORKS = TWICE_REMOVED

Se eu fosse escrever sua função, provavelmente ficaria assim:

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}
    
por 05.06.2014 / 23:27