Variáveis ​​de ambiente não são definidas quando minha função é chamada em um pipeline

10

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 https://bitbucket.org/adalby/monitor-bash se estiver interessado em código completo.

Andrew
fonte

Respostas:

8

Cada parte de um pipeline (ou seja, cada lado do canal) é executada em um processo separado (chamado de subshell, quando um shell bifurca um subprocesso para executar parte do script). Em par_set PIPE FAILS |sed -e's/FAILS/BARFS/', a PIPEvariável é definida no subprocesso que executa o lado esquerdo do pipe. 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 pelos subprocessos.

O lado esquerdo de um tubo sempre é executado em uma subcamada. Algumas cascas (ATT ksh, zsh) passam pelo lado direito nas cascas-mãe; a maioria também executa o lado direito em uma subcasca.

Se você deseja redirecionar a saída de uma parte do script e executá-la no shell pai, em ksh / bash / zsh, é possível usar a substituição de processo .

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 "$@"
Gilles 'SO- parar de ser mau'
fonte
Substituição de processo pela vitória! Eu sabia que poderia usar um pipe nomeado ou um arquivo temporário, mas esses são feios, têm pouca concorrência e deixam uma bagunça para trás se o script morrer (a armadilha ajuda no último). A coisa do espaço é intencional. Por convenção, as variáveis ​​passadas na linha de comando estão em pares nome / valor e são separadas por '=' e / ou ''.
Andrew
3

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

Atualizar:

Parece fácil passar variáveis ​​do pai para o shell filho, mas é muito difícil fazê-lo da outra maneira. Algumas soluções alternativas são denominadas pipes, arquivos temporários, gravação em stdout e leitura no pai, etc.

Algumas referências:

http://mywiki.wooledge.org/BashFAQ/024
/programming//q/15541321/3565972
/programming//a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458979-Como-exportar-variável-no-subshell-back-out-to-parent

savanto
fonte
Eu pensei que era uma possibilidade, mas a função deve ser executada no shell atual. Testei adicionando um "echo $$" à função. par_set ainda falha | sed -e "s / ^ / sedpid = $$, fnpid = /" saídas sedpid = 15957, fnpid = 15957.
Andrew
@Andrew Consulte este stackoverflow.com/a/20726041/3565972 . Aparentemente, $$é o mesmo para os pais e filhos. Você pode usar $BASHPIDpara obter o pid subshell. Quando eu echo $$ $BASHPIDdentro par_seteu recebo diferentes pids.
savanto
@ Andrew Ainda tentando encontrar uma solução alternativa, mas falhando! =)
savanto
@ Savanto-Obrigado. Eu não sabia disso sobre $$ vs $ BASHPID ou o tubo forçando subcascas.
Andrew
0

Você aponta os subshells - que podem ser contornados com alguns detalhes no shell do lado de fora de um pipeline - mas a parte mais difícil do problema tem a ver com a simultaneidade do pipeline .

Todos os membros do processo do pipeline são iniciados de umavez e, portanto, o problema pode ser mais fácil de entender se você olhar assim:

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

Os processos de pipeline não podem herdar os valores da variável porque eles já estão desativados e em execução antes que a variável seja definida.

Eu realmente não consigo entender qual é o objetivo da sua função - a que finalidade ela serve e que exportainda não serve ? Ou 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

Assim, sua coisa pode funcionar como:

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

O qual registraria a sedsaída de um arquivo e entregaria à sua função como uma divisão de 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
}
mikeserv
fonte
Isso é verdade, mas irrelevante: Andrew está tentando usar as variáveis ​​após o término do pipeline, não do outro lado do tubo.
Gilles 'SO- stop be evil'
@ Gilles - bem, ele pode fazer isso. |pipeline | sh
mikeserv
@ Gilles - na verdade, você nem precisa sh. Ele já está usando export.
mikeserv
O objetivo geral é o script de monitoramento do sistema. Gostaria de poder chamá-los interativamente, de outros scripts ou do cron. Deseja poder passar parâmetros definindo envs, passando na linha de comando ou no arquivo de configuração. A função existe para receber entradas de uma ou mais fontes e definir o ambiente corretamente. No entanto, eu também quero poder opcionalmente registrar a saída (depois de executar o sed para formatar), daí a necessidade de canalizar.
18713 Andrew Andrew
1
@ mikeserv- Obrigado por todos os comentários. Fui com a sugestão de substituição de processos de Gilles, mas ter que argumentar sobre meu código me torna um programador melhor.
18715 Andrew Andrew