Zmienne środowiskowe nie są ustawione, gdy moja funkcja jest wywoływana w potoku

10

Mam następującą funkcję rekurencyjną do ustawiania zmiennych środowiskowych:

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

Jeśli wywołam to samo, zarówno ustawia zmienną, jak i echo na standardowe wyjście:

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

Działa także przekierowanie standardowego pliku do pliku:

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

Ale jeśli podłączę standardowe wyjście do innego polecenia, zmienna nie zostanie ustawiona:

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

Dlaczego potok uniemożliwia funkcji eksportowanie zmiennej? Czy istnieje sposób, aby to naprawić bez uciekania się do plików tymczasowych lub nazwanych potoków?

Rozwiązany:

Działający kod dzięki Gillesowi:

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

Umożliwia to wywoływanie skryptu w następujący sposób:

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

Projekt hostowany na bitbucket https://bitbucket.org/adalby/monitor-bash, jeśli jest zainteresowany pełnym kodem.

Andrzej
źródło

Odpowiedzi:

8

Każda część potoku (tj. Każda strona potoku) działa w osobnym procesie (zwanym podpowłoką, gdy powłoka prosi podproces o uruchomienie części skryptu). W par_set PIPE FAILS |sed -e's/FAILS/BARFS/'The PIPEzmienny jest w podprocesu, który wykonuje się w lewej rury. Ta zmiana nie jest odzwierciedlona w procesie nadrzędnym (zmienne środowiskowe nie są przenoszone między procesami, są dziedziczone tylko przez podprocesy.

Lewa strona potoku zawsze działa w podpowłoce. Niektóre powłoki (ATT ksh, zsh) działają po prawej stronie w powłokach macierzystych; większość również działa po prawej stronie w podpowłoce.

Jeśli chcesz przekierować dane wyjściowe części skryptu i uruchomić tę część w powłoce nadrzędnej, w ksh / bash / zsh, możesz użyć podstawienia procesu .

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

Za pomocą dowolnej powłoki POSIX można przekierować dane wyjściowe do nazwanego potoku.

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

Aha, i brakuje ci cudzysłowów wokół podstawień zmiennych , twój kod psuje się na rzeczy takie jak par_set name 'value with spaces' star '*'.

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

par_set "$@"
Gilles „SO- przestań być zły”
źródło
Proces zastępowania wygranej! Wiedziałem, że mogę użyć nazwanego pliku potoku lub pliku tymczasowego, ale te są brzydkie, mają słabą współbieżność i pozostawiają po sobie bałagan, jeśli skrypt umrze (pułapka pomaga w ostatnim). Kosmiczna rzecz jest zamierzona. Zgodnie z konwencją zmienne przekazywane w wierszu poleceń znajdują się w parach nazwa / wartość i są oddzielone znakami „=” i / lub „”.
Andrew
3

To nie działa, ponieważ każda strona potoku działa w podpowłoce w bash, a zmienne ustawione w podpowłoce są lokalne dla tej podpowłoki.

Aktualizacja:

Wygląda na to, że łatwo jest przekazywać zmienne z rodzica do powłoki potomnej, ale naprawdę trudno to zrobić w drugą stronę. Niektóre obejścia to nazwane potoki, pliki tymczasowe, zapisywanie na standardowe wyjście i czytanie w obiekcie nadrzędnym itp.

Niektóre referencje:

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

savanto
źródło
Myślałem, że jest taka możliwość, ale funkcja powinna zostać wykonana w bieżącej powłoce. Testowałem, dodając do funkcji „echo $$”. par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" wypisuje sedpid = 15957, fnpid = 15957.
Andrew
@Andrew Zobacz ten stackoverflow.com/a/20726041/3565972 . Najwyraźniej $$jest tak samo dla powłok nadrzędnych i podrzędnych. Możesz użyć, $BASHPIDaby uzyskać pid podpowłoki. Kiedy jestem echo $$ $BASHPIDw par_setśrodku, dostaję różne stawki.
savanto
@Andrew Wciąż próbuje znaleźć obejście, ale się nie udaje! =)
savanto
@ Savanto-Thanks. Nie wiedziałem, że o $$ vs $ BASHPID lub o wymuszaniu potoku podpowłoki.
Andrew
0

Wskazuje się na podpowłoki - które można obejść z pewnym finansowaniem w powłoce poza potokiem - ale trudniejsza część problemu dotyczy współbieżności potoku .

Wszyscy członkowie procesu potoku rozpoczynają się od razu , więc problem może być łatwiejszy do zrozumienia, jeśli spojrzysz na to w ten sposób:

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

Procesy potokowe nie mogą dziedziczyć wartości zmiennych, ponieważ są one już wyłączone i działają przed ustawieniem zmiennej.

Naprawdę nie rozumiem, jaki jest sens twojej funkcji - jaki cel jej jeszcze nie służy export? A może po prostu var=val? Na przykład, oto znowu prawie ten sam potok:

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

f=4 pipeline

###OUTPUT

4
...
f=7

I z export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Więc twoja rzecz może działać jak:

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

Który zaloguje się na sedwyjściu pliku i dostarczy go do twojej funkcji jako split-split "$@".

Lub alternatywnie:

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

Gdybym jednak miał napisać twoją funkcję, prawdopodobnie wyglądałby tak:

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}
mikeserv
źródło
To prawda, ale bez znaczenia: Andrew próbuje użyć zmiennych po zakończeniu potoku, a nie po drugiej stronie potoku.
Gilles „SO- przestań być zły”
@Gilles - cóż, on może to zrobić. |pipeline | sh
mikeserv
@Gilles - właściwie nawet nie potrzebujesz sh. On już używa export.
mikeserv
Ogólnym celem są skrypty monitorujące system. Chciałbym móc wywoływać je interaktywnie, z innych skryptów lub z crona. Chcesz móc przekazywać parametry, ustawiając zmienne env, przekazując wiersz poleceń lub plik konfiguracyjny. Istnieje funkcja pobierania danych wejściowych z jednego lub więcej źródeł i prawidłowego ustawienia środowiska. Jednak chcę również móc opcjonalnie rejestrować dane wyjściowe (po przejściu przez sed do formatu), stąd potrzeba potokowania.
Andrew
1
@ mikeserv- Dziękujemy za wszystkie komentarze. Poszedłem z sugestią Gilles dotyczącą zastąpienia procesu, ale konieczność argumentowania za moim kodem czyni mnie lepszym programistą.
Andrew