在管道中调用我的函数时未设置环境变量


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 =

为什么管道阻止函数导出变量?有没有办法解决此问题而不求助于临时文件或命名管道?

解决了:

感谢Gilles的工作代码:

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上托管的项目。

Answers:


8

管道的每个部分(即管道的每一侧)都在单独的进程中运行(称为子外壳,当外壳派生一个子进程来运行脚本的一部分时,称为子外壳)。在中par_set PIPE FAILS |sed -e's/FAILS/BARFS/'PIPE变量在执行管道左侧的子过程中设置。此更改未反映在父流程中(环境变量不会在流程之间转移,它们仅由子流程继承。

管道的左侧始终在子外壳中运行。一些外壳程序(ATT ksh,zsh)在父外壳程序的右侧运行;大多数还在子外壳中的右侧运行。

如果要重定向脚本的一部分的输出并在ksh / bash / zsh的父shell中运行该部分,则可以使用进程替换

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

使用任何POSIX Shell,都可以将输出重定向到命名管道。

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

哦,而且您缺少变量替换的引号,因此代码在上中断了par_set name 'value with spaces' star '*'

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

par_set "$@"

工艺替代取胜!我知道我可以使用命名管道或临时文件,但是这些文件很丑陋,并发性很差,如果脚本死了,也会留下一团糟(陷阱有助于最后一个)。太空物是故意的。按照惯例,在命令行上传递的变量是名称/值对,并用'='和/或''分隔。
安德鲁(Andrew)

3

这是行不通的,因为管道的每一面都在中的子外壳中运行bash,并且在子外壳中设置的变量是该子外壳的局部变量。

更新:

看起来很容易将变量从父级传递到子级shell,但实际上很难做到这一点。一些解决方法是命名管道,临时文件,写入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-如何将子外壳中的变量导出回父母


我以为是有可能的,但是该功能应该在当前的shell中执行。我通过在函数中添加“ echo $$”来进行测试。par_set仍然失败| sed的-e “S / ^ / sedpid = $$,fnpid = /”输出sedpid = 15957,fnpid = 15957.
安德鲁

@Andrew请参阅此stackoverflow.com/a/20726041/3565972。显然,$$父外壳和子外壳都相同。您可以$BASHPID用来获取subshel​​l pid。当我echo $$ $BASHPID在里面时par_set,会得到不同的pid。
savanto 2014年

@Andrew仍在尝试寻找解决方法,但是失败了!=)
萨瓦托2014年

@Savanto,谢谢。我不知道关于$$ vs $ BASHPID或管道强制子外壳。
安德鲁(Andrew)

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的输出并将其作为shell拆分传递给您的函数"$@"

或者,或者:

$ 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
}

没错,但这是不相关的:Andrew试图在管道结束之后而不是在管道的另一端使用变量。
吉尔斯(Gillles)“所以-别再邪恶了”

@吉尔斯-好吧,他可以做到。|pipeline | sh
mikeserv

@Gilles-实际上,您甚至不需要sh。他已经在使用export
mikeserv 2014年

总体目标是系统监视脚本。希望能够从其他脚本或cron以交互方式调用它们。希望能够通过设置环境变量,通过命令行或配置文件来传递参数。存在从一个或多个源获取输入并正确设置环境的功能。但是,我还希望能够有选择地记录输出(通过sed格式化后),因此需要管道传输。
安德鲁

1
@ mikeserv-感谢您的所有评论。我同意吉尔斯关于流程替代的建议,但是不得不为我的代码而争论使我成为一个更好的程序员。
安德鲁
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.