関数がパイプラインで呼び出されたときに環境変数が設定されない


10

環境変数を設定する次の再帰関数があります。

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

それを単独で呼び出すと、変数が設定され、標準出力にエコーされます。

$ 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

しかし、標準出力を別のコマンドにパイプすると、変数が設定されません。

$ 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でホストされているプロジェクト。

回答:


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 "$@"

勝利のためのプロセス代替!名前付きパイプまたは一時ファイルを使用できることはわかっていましたが、醜く、同時実行性が低く、スクリプトが停止した場合でも混乱が残ります(トラップは最後のファイルで役立ちます)。宇宙のものは意図的です。慣例により、コマンドラインで渡される変数は、名前と値のペアであり、「=」または「/」で区切られます。
Andrew

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-How-export-variable-in-subshel​​l-back-out-to-parent


それは可能性があると思いましたが、関数は現在のシェルで実行する必要があります。関数に「エコー$$」を追加してテストしました。par_setはまだ失敗| "S / ^ / sedpid = $$、fnpid = /"出力sedpid = 15957、fnpid = 15957. -e sedの
アンドリュー・

@Andrewこのstackoverflow.com/a/20726041/3565972を参照してください。どうやら、$$親シェルと子シェルの両方で同じです。$BASHPIDサブシェルpidを取得するために使用できます。私のecho $$ $BASHPIDpar_setにいるとき、私は異なるpidを取得します。
savanto 2014年

@Andrewまだ回避策を見つけようとしていますが、失敗しました!=)
savanto 2014年

@ Savanto-ありがとう。$$と$ 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の出力にログを記録し、それをシェル分割として関数に配信します"$@"

または、代わりに:

$ 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は、パイプラインの反対側ではなく、パイプラインの終了後に変数を使用しようとしています。
Gilles 'SO-邪悪なことをやめなさい'

@ギレス-まあ彼はそれを行うことができます。|pipeline | sh
mikeserv 2014

@ギレス-実際には、必要もありませんsh。彼はすでにを使用していexportます。
mikeserv 2014

全体的な目標は、システム監視スクリプトです。対話的に、他のスクリプトから、またはcronからそれらを呼び出すことができるようにしたいと思います。env varsを設定し、コマンドラインまたは設定ファイルを渡してパラメーターを渡せるようにしたい。1つ以上のソースから入力を受け取り、環境を正しく設定する機能が存在します。ただし、オプションで出力をログに記録できるようにしたいので(sedを実行してフォーマットした後)、パイプする必要があります。
Andrew

1
@ mikeserv-すべてのコメントをありがとう。私はGilesのプロセス置換の提案に同意しましたが、私のコードについて議論する必要があるため、私はより優れたプログラマーになります。
Andrew
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.