Las variables de entorno no se establecen cuando se llama a mi función en una tubería

10

Tengo la siguiente función recursiva para establecer variables de entorno:

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

Si lo llamo solo, establece la variable y hace eco en stdout:

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

Redirigir stdout a un archivo también funciona:

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

Pero, si canalizo stdout a otro comando, la variable no se establece:

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

¿Por qué la tubería impide que la función exporte la variable? ¿Hay alguna manera de solucionar esto sin recurrir a archivos temporales o canalizaciones con nombre?

Resuelto:

Código de trabajo gracias a Gilles:

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

Esto permite que el script se llame así:

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

Proyecto alojado en bitbucket https://bitbucket.org/adalby/monitor-bash si está interesado en el código completo.

Andrés
fuente

Respuestas:

8

Cada parte de una tubería (es decir, cada lado de la tubería) se ejecuta en un proceso separado (llamado subshell, cuando un shell bifurca un subproceso para ejecutar parte del script). En par_set PIPE FAILS |sed -e's/FAILS/BARFS/', la PIPEvariable se establece en el subproceso que ejecuta el lado izquierdo de la tubería. Este cambio no se refleja en el proceso padre (las variables de entorno no se transfieren entre procesos, solo las heredan los subprocesos).

El lado izquierdo de una tubería siempre se ejecuta en una subshell. Algunos shells (ATT ksh, zsh) se ejecutan en el lado derecho en los shells principales; la mayoría también ejecuta el lado derecho en una subshell.

Si desea redirigir la salida de una parte del script y ejecutar esa parte en el shell principal, en ksh / bash / zsh, puede usar la sustitución del proceso .

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

Con cualquier shell POSIX, puede redirigir la salida a una tubería con nombre.

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

Ah, y te faltan citas sobre sustituciones variables , tu código se rompe en cosas como par_set name 'value with spaces' star '*'.

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

par_set "$@"
Gilles 'SO- deja de ser malvado'
fuente
Proceso de sustitución para la victoria! Sabía que podía usar una tubería con nombre o un archivo temporal, pero son feos, tienen poca concurrencia y dejan un desastre si el script muere (la trampa ayuda con el último). Lo espacial es intencional. Por convención, las variables pasadas en la línea de comando están en pares de nombre / valor y separadas por '=' y / o ''.
Andrew
3

Esto no funciona porque cada lado de la tubería se ejecuta en una subshell bash, y las variables establecidas en una subshell son locales para esa subshell.

Actualizar:

Parece que es fácil pasar variables del shell principal al secundario, pero es realmente difícil hacerlo de la otra manera. Algunas soluciones se denominan canalizaciones, archivos temporales, escritura en stdout y lectura en el padre, etc.

Algunas referencias:

http://mywiki.wooledge.org/BashFAQ/024
/programming//q/15541321/3565972
/programming//a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458979-How-export-variable-in-subshell-back-out-to-parent

savanto
fuente
Pensé que era una posibilidad, pero la función debería ejecutarse en el shell actual. Probé agregando un "echo $$" a la función. par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" salidas sedpid = 15957, fnpid = 15957.
Andrew
@ Andrew Ver este stackoverflow.com/a/20726041/3565972 . Aparentemente, $$es lo mismo para los caparazones de padres e hijos. Puede usar $BASHPIDpara obtener la subshell pid. Cuando en mi echo $$ $BASHPIDinterior par_setobtengo diferentes pids.
savanto
@Andrew Todavía intenta encontrar una solución, ¡pero falla! =)
savanto
@ Savanto-Gracias. No sabía eso sobre $$ vs $ BASHPID o las subcapas de forzado de tuberías.
Andrew
0

Señala las subcapas, que se pueden solucionar con algunos cambios en el caparazón fuera de una tubería, pero la parte más difícil del problema tiene que ver con la concurrencia de la tubería .

Todos los miembros del proceso de la tubería comienzan a la vez , por lo que el problema podría ser más fácil de entender si lo mira así:

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

Los procesos de canalización no pueden heredar los valores de las variables porque ya están desactivados y ejecutándose antes de que la variable se establezca.

Realmente no puedo entender cuál es el punto de su función: ¿para qué sirve que exportya no sirve? O incluso solo var=val? Por ejemplo, aquí está casi la misma tubería nuevamente:

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

f=4 pipeline

###OUTPUT

4
...
f=7

Y con export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Entonces tu cosa podría funcionar así:

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

Lo cual se registraría en sedla salida de un archivo y lo entregaría a su función como una división de shell "$@".

O alternativamente:

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

Sin embargo, si fuera a escribir su función, probablemente se vería así:

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}
mikeserv
fuente
Eso es cierto, pero irrelevante: Andrew está tratando de usar las variables después de que finaliza la tubería, no en el otro lado de la tubería.
Gilles 'SO- deja de ser malvado'
@Gilles: bueno, él puede hacer eso. |pipeline | sh
mikeserv
@Gilles: en realidad, ni siquiera lo necesitas sh. El ya esta usando export.
mikeserv
El objetivo general es guiones de monitoreo del sistema. Me gustaría poder llamarlos de forma interactiva, desde otros scripts o desde cron. Desea poder pasar parámetros configurando env vars, pasando la línea de comando o el archivo de configuración. La función existe para recibir información de una o más fuentes y configurar el entorno correctamente. Sin embargo, también quiero poder registrar opcionalmente la salida (después de ejecutar a través de sed para formatear), de ahí la necesidad de canalizar.
Andrew
1
@ mikeserv- Gracias por todos los comentarios. Fui con la sugerencia de Gilles de sustitución del proceso, pero tener que defender mi código me convierte en un mejor programador.
Andrew