Змінні середовища не встановлюються, коли моя функція викликається в конвеєрі


10

У мене є така рекурсивна функція для встановлення змінних середовища:

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

Якщо я називаю це сам по собі, він обидва встановлює змінну і відлунює для stdout:

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

Перенаправлення stdout до файлу також працює:

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

Але, якщо я передаю stdout іншій команді, змінна не встановлюється:

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

Чому труба заважає функції експорту змінної? Чи є спосіб виправити це, не вдаючись до тимчасових файлів або названих труб?

Вирішено:

Робочий код завдяки Жилу:

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

Це дозволяє таким чином викликати сценарій:

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

Проект розміщений на Bitbucket https://bitbucket.org/adalby/monitor-bash, якщо зацікавлений у повному коді.

Відповіді:


8

Кожна частина конвеєра (тобто кожна сторона труби) працює в окремому процесі (так званому підпакеті, коли оболонка розщеплює підпроцес для запуску частини сценарію). В par_set PIPE FAILS |sed -e's/FAILS/BARFS/', PIPEзмінна встановлюється в підпроцесі, який виконує ліву частину труби. Ця зміна не відображається в батьківському процесі (змінні середовища не передаються між процесами, вони успадковуються лише підпроцесами.

Ліва частина труби завжди проходить в нижній частині корпусу. Деякі оболонки (ATT ksh, zsh) запускають правий бік у батьківських оболонках; більшість також запускають праву частину в нижній частині корпусу.

Якщо ви хочете обидва перенаправити вихід частини скрипту і запустити цю частину в батьківській оболонці, в ksh / bash / zsh, ви можете використовувати підстановку процесу .

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

За допомогою будь-якої оболонки POSIX ви можете перенаправити вихід на названу трубу.

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

О, і вам не вистачає цитат навколо змінних підстановок , ваш код порушується на такі речі par_set name 'value with spaces' star '*'.

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

par_set "$@"

Процес заміни на виграш! Я знав, що можу використовувати названий файл pipe або temp, але вони некрасиві, мають погану одночасність і залишають безлад, якщо сценарій загине (пастка допомагає з останнім). Космічна річ є навмисною. За умовою, змінні, передані в командному рядку, знаходяться в парах імен / значень і розділяються '=' та / або ''.
Андрій

3

Це не працює, оскільки кожна сторона труби працює в підклітині bash, а змінні, встановлені в підклітині, є локальними для цієї підклітини.

Оновлення:

Схоже, легко передавати змінні від батьківського до дочірньої оболонки, але насправді важко зробити це іншим способом. Деякі обхідні шляхи називаються трубами, тимчасовими файлами, записом у stdout та читанням у батьківській програмі тощо.

Деякі посилання:

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-Як-експорт-змінна-в-підпакеті-назад-до-батьківського


Я думав, що це є можливістю, але функція повинна виконуватись у поточній оболонці. Я перевірив, додавши функцію "echo $$". par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" виводить sedpid = 15957, fnpid = 15957.
Андрій

@ Андрій Подивитися цей stackoverflow.com/a/20726041/3565972 . Мабуть, $$однаково і для батьківських, і для дитячих оболонок. Ви можете використовувати , $BASHPIDщоб отримати подоболочкі PID. Коли я echo $$ $BASHPIDвсередині, par_setя отримую різні приставки.
саванто

@Andrew Тим не менш намагається знайти вирішення, але не вдалося! =)
савато

@ Savanto-Спасибі. Я не знав, що про $$ vs $ BASHPID або про трубу, що примушує передплати.
Андрій

0

Ви вказуєте на допоміжні оболонки - які можна обійти, дещо завершується в оболонці поза трубопроводу - але важча частина проблеми пов'язана з паралельністю трубопроводу .

Усі члени процесу трубопроводу починаються одразу , і тому проблему може бути легше зрозуміти, якщо подивитися на це так:

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

Процеси конвеєра не можуть успадкувати значення змінних, оскільки вони вже вимкнено та запущені до того, як змінна колись буде встановлена.

Я не можу реально зрозуміти, у чому полягає ваша функція - якій цілі вона служить, що exportвже не працює? Або навіть просто var=val? Наприклад, ось майже такий же трубопровід знову:

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

f=4 pipeline

###OUTPUT

4
...
f=7

І з export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

Отже, ваша річ може працювати так:

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

Що б увійти до sedвиводу файлу і доставити його вашій функції як розділений на оболонку "$@".

Або, як варіант:

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

Якби я збирався написати вашу функцію, це, мабуть, виглядатиме так:

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

Це правда, але не має значення: Ендрю намагається використовувати змінні після закінчення конвеєра, а не з іншого боку труби.
Жил 'SO- перестань бути злим'

@Gilles - добре він може це зробити. |pipeline | sh
mikeserv

@Gilles - насправді вам навіть не потрібно sh. Він уже використовує export.
mikeserv

Загальна мета - сценарії системного моніторингу. Хочеться мати можливість зателефонувати їм інтерактивно, з інших сценаріїв або з крона. Хочете мати можливість передавати параметри, встановивши env vars, передаючи командний рядок або конфігураційний файл. Існує функція приймати дані з одного або декількох джерел і правильно встановлювати середовище. Тим не менш, я також хочу мати можливість додатково занотувати вихід (після запуску через sed до формату), отже, необхідність передачі.
Андрій

1
@ mikeserv- Дякую за всі коментарі. Я пішов із пропозицією Жилла про заміну процесу, але необхідність сперечатися з кодом робить мене кращим програмістом.
Андрій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.