Переменные окружения не устанавливаются, когда моя функция вызывается в конвейере

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 "$@"
Жиль "ТАК - перестань быть злым"
источник
Процесс замены на победу! Я знал, что могу использовать именованный канал или временный файл, но они ужасны, имеют плохой параллелизм и оставляют беспорядок, если сценарий умирает (ловушка помогает с последним). Космическая вещь является намеренной. По соглашению переменные, передаваемые в командной строке, находятся в парах имя / значение и разделяются символами '=' и / или ''.
Андрей
3

Это не работает, потому что каждая сторона канала работает в подоболочке bash, а переменные, заданные в подоболочке, являются локальными для этой подоболочки.

Обновить:

Похоже, что легко передать переменные из родительского в дочернюю оболочку, но действительно трудно сделать это другим способом. Некоторые обходные пути: именованные каналы, временные файлы, запись в стандартный вывод, чтение в родительском файле и т. Д.

Некоторые ссылки:

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-Как-экспорт переменной-в-субоболочке-обратно из к родителям

savanto
источник
Я думал, что это возможно, но функция должна выполняться в текущей оболочке. Я проверил, добавив "echo $$" к функции. par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /" выводит sedpid = 15957, fnpid = 15957.
Андрей
@Andrew Смотрите этот stackoverflow.com/a/20726041/3565972 . Видимо, $$одинаково как для родительских, так и для дочерних оболочек. Вы можете использовать, $BASHPIDчтобы получить pid subshell. Когда я echo $$ $BASHPIDвнутри, par_setя получаю разные пид.
Саванто
@ Андрей все еще пытается найти обходной путь, но не удается! =)
Саванто
@ 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
}
mikeserv
источник
Это правда, но не имеет значения: Эндрю пытается использовать переменные после окончания конвейера, а не по другую сторону трубы.
Жиль "ТАК - перестань быть злым"
@ Жиль - ну, он может это сделать. |pipeline | sh
mikeserv
@ Жиль - на самом деле, тебе даже не нужно sh. Он уже использует export.
mikeserv
Общая цель - скрипты мониторинга системы. Хотел бы иметь возможность вызывать их интерактивно, из других скриптов или из cron. Хотите иметь возможность передавать параметры путем установки переменных env, передачи в командной строке или файла конфигурации. Функция существует для получения входных данных от одного или нескольких источников и правильной настройки среды. Тем не менее, я также хочу иметь возможность дополнительно регистрировать вывод (после запуска через sed для форматирования), следовательно, необходимо передавать по конвейеру.
Андрей
1
@ mikeserv- Спасибо за все комментарии. Я согласился с предложением Жиля о замене процесса, но необходимость отстаивать свой код делает меня лучшим программистом.
Андрей