Le variabili di ambiente non vengono impostate quando la mia funzione viene chiamata in una pipeline


10

Ho la seguente funzione ricorsiva per impostare le variabili di ambiente:

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

Se lo chiamo da solo, imposta sia la variabile che l'eco su stdout:

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

Il reindirizzamento di stdout su un file funziona anche:

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

Ma, se installo stdout a un altro comando, la variabile non viene impostata:

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

Perché la pipe impedisce alla funzione di esportare la variabile? C'è un modo per risolvere questo problema senza ricorrere a file temporanei o named pipe?

risolto:

Codice di lavoro grazie a Gilles:

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

Ciò consente allo script di essere chiamato in questo modo:

$ . ./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

Progetto ospitato su bitbucket https://bitbucket.org/adalby/monitor-bash se interessati al codice completo.

Risposte:


8

Ogni parte di una condotta (cioè ogni lato del tubo) viene eseguito in un processo separato (chiamato sottoshell, quando un colpo biforca un sottoprocesso per eseguire parte dello script). In par_set PIPE FAILS |sed -e's/FAILS/BARFS/', la PIPEvariabile è impostata nel sottoprocesso che esegue il lato sinistro della pipe. Questa modifica non si riflette nel processo padre (le variabili di ambiente non si trasferiscono tra i processi, sono ereditate solo dai sottoprocessi.

Il lato sinistro di una pipe viene sempre eseguito in una subshell. Alcune shell (ATT ksh, zsh) eseguono il lato destro nelle shell parent; la maggior parte esegue anche il lato destro in una subshell.

Se si desidera sia reindirizzare l'output di una parte dello script sia eseguire quella parte nella shell padre, in ksh / bash / zsh, è possibile utilizzare la sostituzione del processo .

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

Con qualsiasi shell POSIX, è possibile reindirizzare l'output su una pipe denominata.

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

Oh, e ti mancano le virgolette intorno alle sostituzioni variabili , il tuo codice si rompe su cose come par_set name 'value with spaces' star '*'.

export "${PAR}=${VAL}"

par_set "$@"

Sostituzione del processo per la vittoria! Sapevo di poter usare un file pipe o temp con nome, ma quelli sono brutti, hanno scarsa concorrenza e lasciano un casino se lo script muore (trap aiuta con l'ultimo). Lo spazio è intenzionale. Per convenzione, le variabili passate sulla riga di comando sono in coppie nome / valore e separate da '=' e / o ''.
Andrew,

3

Questo non funziona perché ogni lato della pipe viene eseguito in una subshell bashe le variabili impostate in una subshell sono locali per quella subshell.

Aggiornare:

Sembra che sia facile passare le variabili dal genitore alla shell figlio, ma è davvero difficile farlo nell'altro modo. Alcune soluzioni alternative sono denominate pipe, file temporanei, scrittura su stdout e lettura nel genitore, ecc.

Alcuni riferimenti:

http://mywiki.wooledge.org/BashFAQ/024
https://stackoverflow.com/q/15541321/3565972
https://stackoverflow.com/a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458.979-How-export-variabili-in-subshell-back-out-to-genitore


Ho pensato che fosse una possibilità, ma la funzione dovrebbe essere eseguita nella shell corrente. Ho provato aggiungendo un "echo $$" alla funzione. par_set ANCORA FALLI | sed -e "s / ^ / sedpid = $$, fnpid = /" output sedpid = 15957, fnpid = 15957.
Andrew

@Andrew Vedere questo stackoverflow.com/a/20726041/3565972 . Apparentemente, $$è lo stesso per entrambi i gusci padre e figlio. È possibile utilizzare $BASHPIDper ottenere il pid subshell. Quando echo $$ $BASHPIDentro par_setho diversi pid.
Savanto,

@Andrew Sto ancora cercando di trovare una soluzione alternativa, ma non ci riesco! =)
savanto,

@ Savanto-Grazie. Non sapevo che circa $$ vs $ BASHPID o le pipe che costringessero i subshells.
Andrew,

0

Fai notare i sottotitoli - che possono essere aggirati con un po 'di finagling nella shell all'esterno di una pipeline - ma la parte più difficile del problema ha a che fare con la concorrenza della pipeline .

Tutti i membri del processo della pipeline iniziano contemporaneamente , quindi il problema potrebbe essere più semplice da capire se lo si osserva in questo modo:

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

I processi della pipeline non possono ereditare i valori delle variabili perché sono già disattivati ​​e in esecuzione prima che la variabile venga mai impostata.

Non riesco davvero a capire quale sia il punto della tua funzione: a quale scopo serve exportgià? O anche solo var=val? Ad esempio, ecco di nuovo quasi la stessa pipeline:

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

f=4 pipeline

###OUTPUT

4
...
f=7

E con export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Quindi la tua cosa potrebbe funzionare come:

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

Che accederà sedall'output di un file e lo consegnerà alla tua funzione come shell-split "$@".

Oppure, in alternativa:

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

Se avessi scritto la tua funzione, probabilmente sarebbe stata così:

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

È vero, ma irrilevante: Andrew sta cercando di utilizzare le variabili dopo la fine della pipeline, non dall'altra parte della pipa.
Gilles 'SO- smetti di essere malvagio' il

@Gilles - beh, può farlo. |pipeline | sh
Mikeserv,

@Gilles - in realtà, non hai nemmeno bisogno sh. Sta già usando export.
Mikeserv,

L'obiettivo generale sono gli script di monitoraggio del sistema. Vorrei essere in grado di chiamarli in modo interattivo, da altri script o da cron. Vuoi essere in grado di passare i parametri impostando var, passando sulla riga di comando o il file di configurazione. Esiste una funzione per ricevere input da una o più fonti e impostare l'ambiente in modo corretto. Tuttavia, voglio anche essere in grado di registrare facoltativamente l'output (dopo aver eseguito sed per formattare), quindi la necessità di eseguire il pipe.
Andrew

1
@ mikeserv- Grazie per tutti i commenti. Ho seguito il suggerimento di Gilles sulla sostituzione del processo, ma dovendo discutere per il mio codice mi rende un programmatore migliore.
Andrew,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.