web-dev-qa-db-ja.com

シェルスクリプトは呼び出し元のシェルの環境変数を設定できますか?

実行すると、呼び出し元のシェルに設定されたままになるいくつかの環境変数を設定するシェルスクリプトを作成しようとしています。

setenv FOO foo

csh/tcshで、または

export FOO=foo

sh/bashでは、スクリプトの実行中にのみ設定されます。

私はすでにそれを知っています

source myscript

新しいシェルを起動するのではなく、スクリプトのコマンドを実行します。その結果、「呼び出し側」の環境が設定される可能性があります。

しかし、これがこすりです:

このスクリプトをbashまたはcshのどちらからでも呼び出せるようにします。つまり、どちらのシェルのユーザーも自分のスクリプトを実行してシェルの環境を変更できるようにする必要があります。 cshを実行しているユーザーはbashスクリプトを実行できず、bashを実行しているユーザーはcshスクリプトを実行できないため、 'source'は私には機能しません。

スクリプトに2つのバージョンを書いて保守する必要がないという合理的な解決策はありますか?

373
Larry Gritz

シェルプロセスには親の環境のコピーがあり、親プロセスの環境にはまったくアクセスできません。シェルプロセスが終了すると、その環境に加えた変更はすべて失われます。スクリプトファイルの入手は、シェル環境を構成するために最も一般的に使用される方法です。弾丸を噛んで、シェルの2つのフレーバーそれぞれに1つを維持したい場合があります。

239
converter42

「ドットスペーススクリプト」呼び出し構文を使用してください。たとえば、スクリプトへのフルパスを使用してこれを行う方法は次のとおりです。

. /path/to/set_env_vars.sh

スクリプトと同じディレクトリにいる場合は、次のようにします。

. set_env_vars.sh

これらは別のものをロードする代わりに現在のシェルの下でスクリプトを実行します(これは./set_env_vars.shを実行した場合に起こることです)。同じシェル内で実行されるため、設定した環境変数は終了時に使用可能になります。

これはsource set_env_vars.shを呼び出すのと同じことですが、入力するのが短く、sourceが動作しない場所では動作するかもしれません。

275
Humberto Romero

呼び出し側のシェルは別のプロセスコンテキストにあるため、呼び出し側のシェルを変更することはできません。子プロセスがシェルの変数を継承すると、それらはコピー自体を継承します。

あなたができることの一つは、それが呼び出された方法に基づいてtcshまたはshのための正しいコマンドを発行するスクリプトを書くことです。スクリプトが "setit"の場合は、次のようにします。

ln -s setit setit-sh

そして

ln -s setit setit-csh

直接またはエイリアスで今、あなたはshからこれを行います

eval `setit-sh`

またはこれはcshから

eval `setit-csh`

setitはその出力スタイルを決定するために$ 0を使います。

これは、TERM環境変数セットを取得するために人々がどのように使用するのかを思い出させます。

ここでの利点は、setitが以下のように好きなシェルで書かれていることです。

#!/bin/bash
arg0=$0
arg0=${arg0##*/}
for nv in \
   NAME1=VALUE1 \
   NAME2=VALUE2
do
   if [ x$arg0 = xsetit-sh ]; then
      echo 'export '$nv' ;'
   Elif [ x$arg0 = xsetit-csh ]; then
      echo 'setenv '${nv%%=*}' '${nv##*=}' ;'
   fi
done

上記のシンボリックリンクとバッククォートされた式の評価により、これは望ましい結果をもたらします。

Csh、tcsh、または類似のシェルの呼び出しを簡単にするには、次のようにします。

alias dosetit 'eval `setit-csh`'

またはsh、bashなどの場合

alias dosetit='eval `setit-sh`'

1つ良いところは、リストを1か所に管理するだけでいいということです。理論的には、リストをファイルに貼り付けてcat nvpairfilenameを "in"と "do"の間に入れることもできます。

これは、ログインシェルの端末設定が行われた方法とほぼ同じです。スクリプトは、ログインシェルで実行されるステートメントを出力します。 "tset vt100"のように、呼び出しを簡単にするためにエイリアスが一般的に使用されます。別の回答で述べたように、INN UseNetニュースサーバーにも同様の機能があります。

53
Thomas Kammeyer

私の.bash_profileには、次のものがあります。

# No Proxy
function noproxy
{
    /usr/local/sbin/noproxy  #turn off proxy server
    unset http_proxy HTTP_PROXY https_proxy HTTPs_PROXY
}


# Proxy
function setproxy
{
    sh /usr/local/sbin/proxyon  #turn on proxy server 
    http_proxy=http://127.0.0.1:8118/
    HTTP_PROXY=$http_proxy
    https_proxy=$http_proxy
    HTTPS_PROXY=$https_proxy
    export http_proxy https_proxy HTTP_PROXY HTTPS_PROXY
}

だから私はプロキシを無効にしたいときは、関数はログインシェルで実行され、期待どおりに変数を設定します。

44
chris

Gdbと setenv(3) を使用することで可能になるのは「類の」ことですが、実際にこれを行うことを推奨するのは困難です。 (さらに、つまり最近のubuntuでは、ptraceについてもっと寛容になるようにカーネルに指示せずに実際にこれを実行させることはできません。他のディストリビューションでも同様です)。

$ cat setfoo
#! /bin/bash

gdb /proc/${PPID}/exe ${PPID} <<END >/dev/null
call setenv("foo", "bar", 0)
END
$ echo $foo

$ ./setfoo
$ echo $foo
bar
25

これはうまくいきます - それは私が使うものではありませんが、それは「うまくいきます」。環境変数Teredo_WORMSを設定するスクリプトTeredoを作成しましょう。

#!/bin/ksh
export Teredo_WORMS=ukelele
exec $Shell -i

これはKornシェルによって解釈され、環境変数をエクスポートしてから、新しい対話型シェルに置き換えられます。

このスクリプトを実行する前に、環境内でShellがCシェルに設定されており、環境変数Teredo_WORMSが設定されていません。

% env | grep Shell
SHELL=/bin/csh
% env | grep Teredo
%

スクリプトが実行されると、新しいシェル、もう1つの対話型Cシェルに入りますが、環境変数は設定されています。

% Teredo
% env | grep Teredo
TEREDO_WORMS=ukelele
%

このシェルから出ると、元のシェルが引き継ぎます。

% exit
% env | grep Teredo
%

環境変数が元のシェルの環境に設定されていません。 exec Teredoを使用してコマンドを実行すると、元の対話型シェルが環境を設定するKornシェルに置き換えられ、次にそれが新しい対話型Cシェルに置き換えられます。

% exec Teredo
% env | grep Teredo
TEREDO_WORMS=ukelele
%

exit(または Control-Dその後、あなたのシェルは終了し、おそらくあなたはそのウィンドウからログアウトするか、実験が始まったところから以前のレベルのシェルにあなたを連れ戻します。

同じメカニズムがBashまたはKorn Shellでも機能します。あなたはexitコマンドの後にプロンプ​​トが面白い場所に現れるのを見つけるかもしれません。


コメント内の議論に注意してください。これは私がお勧めする解決策ではありませんが、すべてのシェル(対話型シェルを作成するための-iオプションを受け入れる)で動作する環境を設定するという単一のスクリプトという目的を達成します。オプションの後に"$@"を追加して他の引数を中継することもできます。これにより、シェルを一般的な「環境の設定およびコマンドの実行」ツールとして使用できるようになります。他の引数がある場合は-iを省略することをお勧めします。

#!/bin/ksh
export Teredo_WORMS=ukelele
exec $Shell "${@-'-i'}"

"${@-'-i'}"ビットは、 '引数リストに少なくとも1つの引数が含まれる場合は、元の引数リストを使用することを意味します。それ以外の場合は、存在しない引数を-iに置き換えます。

12

あなたはモジュールを使うべきです。 http://modules.sourceforge.net/ /を見てください。

編集:modulesパッケージは2012年以来更新されていませんが、それでも基本的にはうまく動作します。すべての新機能、鐘と笛は今日lmodで起こります(私はそれがもっと好きです): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

10
Davide

私が言及していないもう一つの回避策はファイルに変数値を書くことです。

私は非常によく似た問題に遭遇しました。そこでは、(私のすべてのテストではなく)最後のセットのテストを実行できるようにしたいと思いました。私の最初の計画は環境変数TESTCASEを設定するための1つのコマンドを書いて、それからテストを実行するためにこれを使うだろう別のコマンドを持つことでした。私があなたと同じ正確な問題を抱えていたことは言うまでもない。

しかし、それから私はこの簡単なハックを思い付きました:

最初のコマンド(testset):

#!/bin/bash

if [ $# -eq 1 ]
then
  echo $1 > ~/.TESTCASE
  echo "TESTCASE has been set to: $1"
else
  echo "Come again?"
fi

2番目のコマンド(testrun):

#!/bin/bash

TESTCASE=$(cat ~/.TESTCASE)
drush test-run $TESTCASE
8
dkinzer

Bashスクリプトの先頭に-lフラグを追加してください。

#!/usr/bin/env bash -l

...

export NAME1="VALUE1"
export NAME2="VALUE2"

NAME1NAME2の値は現在の環境にエクスポートされていますが、これらの変更は恒久的なものではありません。それらを恒久的にしたい場合は、それらを.bashrcファイルまたは他のinitファイルに追加する必要があります。

Manページから:

-l Make bash act as if it had been invoked as a login Shell (see INVOCATION below).
4
cristobal

子プロセスにその環境変数を(「env」を呼び出すことによって)印刷し、次に親プロセス内の印刷された環境変数をループして、それらの変数に対して「export」を呼び出すように指示できます。

次のコードは、 findの出力の取得に基づいています。 -print0をbash配列に

親シェルがbashの場合は、

while IFS= read -r -d $'\0' line; do
    export "$line"
done < <(bash -s <<< 'export VARNAME=something; env -0')
echo $VARNAME

親シェルがダッシュの場合、readは-dフラグを提供せず、コードはより複雑になります。

TMPDIR=$(mktemp -d)
mkfifo $TMPDIR/fifo
(bash -s << "EOF"
    export VARNAME=something
    while IFS= read -r -d $'\0' line; do
        echo $(printf '%q' "$line")
    done < <(env -0)
EOF
) > $TMPDIR/fifo &
while read -r line; do export "$(eval echo $line)"; done < $TMPDIR/fifo
rm -r $TMPDIR
echo $VARNAME
3
klaus se

別のbashを別のbash_profileで呼び出すことができます。また、マルチbashprofile環境で使用するための特別なbash_profileを作成することもできます。

Bashprofileの中で functions を使うことができ、その関数はグローバルに利用できるようになります。たとえば、 "function user {export USER_NAME $ 1}"は実行時に変数を設定できます。次に例を示します。 grep olegchir

2
Oleg Chirukhin

私は何年も前にこれをやった。正しく覚えていれば、.bashrcと.cshrcのそれぞれに別名をパラメーターと共に含めて、環境を共通形式に設定するそれぞれの形式を別名設定しました。

それから、2つのシェルのいずれかであなたがソースにするスクリプトはその最後の形式を持つコマンドを持っています。

具体的なエイリアスが見つかった場合は、それらを投稿します。

1
sancho.s

簡単な答えは「いいえ」です。親プロセスの環境を変更することはできませんが、カスタム環境変数とユーザーが選択したシェルを使用した環境が欲しいようです。

では、なぜこんな感じではない

#!/usr/bin/env bash
FOO=foo $Shell

それで、あなたが環境を使い終わったら、exitだけです。

1
Andrew

私はパイプ、eval、およびsignalを使用してソリューションを作成しました。

parent() {
    if [ -z "$G_EVAL_FD" ]; then
            die 1 "Rode primeiro parent_setup no processo pai"
    fi
    if [ $(ppid) = "$$" ]; then
            "$@"
    else
            kill -SIGUSR1 $$
            echo "$@">&$G_EVAL_FD
    fi
}
parent_setup() {
    G_EVAL_FD=99
    tempfile=$(mktemp -u)
    mkfifo "$tempfile"
    eval "exec $G_EVAL_FD<>'$tempfile'"
    rm -f "$tempfile"
    trap "read CMD <&$G_EVAL_FD; eval \"\$CMD\"" USR1
}
parent_setup #on parent Shell context
( A=1 ); echo $A # prints nothing
( parent A=1 ); echo $A # prints 1

どんなコマンドでも動作するかもしれません。

OS X bashでは、以下のことができます。
変数を設定解除するためのbashスクリプトファイルを作成します。

#!/bin/bash
unset http_proxy

ファイルを実行可能にする

Sudo chmod 744 unsetvar

エイリアスを作成

alias unsetvar='source /your/path/to/the/script/unsetvar'

スクリプトファイルを含むフォルダがパスに追加されていれば、すぐに使用できます。

1
Marton Tatai

あなたはいつもエイリアスを使うことができます

alias your_env='source ~/scripts/your_env.sh'
1
user1667208

技術的には、それは正しいです - 'eval'だけが他のシェルをフォークしません。ただし、変更された環境で実行しようとしているアプリケーションの観点からは、違いはありません。子はその親の環境を継承するため、(変更された)環境はすべての下位プロセスに伝達されます。

事実上、親プログラム/シェルの下で実行している限り、変更された環境変数は「固執」します。

親(Perlまたはシェル)が終了した後も環境変数を残すことが絶対に必要な場合は、親シェルが重い作業をする必要があります。私がドキュメントで見た1つの方法は、現在のスクリプトが必要な 'エクスポート'言語で実行可能ファイルを生成し、それからそれを実行するために親シェルをだますことです - 常にあなたが前書きする必要があるという事実変更された環境の不揮発性バージョンを残したい場合は、 'source'を指定してコマンドを実行してください。せいぜいKluge。

2番目の方法は、変更されたパラメーターを含むようにシェル環境(.bashrcなど)を開始するスクリプトを変更することです。これは危険な場合があります - 初期化スクリプトを起動すると、次回シェルを起動しようとしたときにシェルが使用できなくなる可能性があります。現在のシェルを変更するためのツールはたくさんあります。必要な調整を「ランチャー」に固定することで、効果的にそれらの変更を前に進めることができます。一般的には良い考えではありません。特定のアプリケーションスイートの環境変更だけが必要な場合は、戻ってShell起動スクリプトを元の状態(viなどを使用して)に戻す必要があります。

つまり、良い(そして簡単な)方法はありません。おそらくこれは、システムのセキュリティが取り返しのつかないほど妥協されないようにすることを確実にするのを困難にしました。

1
David Lovering

他の選択肢は "Environment Modules"( http://modules.sourceforge.net/ )を使うことです。これは残念ながらミックスに第三言語を導入します。 Tclの言語で環境を定義しますが、典型的な修正(prepend vs append vs set)のための便利なコマンドがいくつかあります。環境モジュールもインストールする必要があります。その後、module load *XXX*を使用して必要な環境を指定できます。 moduleコマンドは、基本的にはThomas Kammeyerによる上記のevalメカニズムの派手なエイリアスです。ここでの主な利点は、あなたが環境を一つの言語で維持し、それをsh、ksh、bash、csh、tcsh、zsh、python(?!?!!)などに翻訳するために "Environment Modules"に頼ることができるということです。

1
Howard Hobbes

協調的なプロセスでこの問題を回避する方法を文書化した回答はありません。 ssh-agentのようなものと共通のパターンは、子プロセスが親がevalにできる式を出力することです。

bash$ eval $(shh-agent)

たとえば、ssh-agentには、CshまたはBourne互換の出力構文を選択するオプションがあります。

bash$ ssh-agent
SSH2_AUTH_SOCK=/tmp/ssh-era/ssh2-10690-agent; export SSH2_AUTH_SOCK;
SSH2_AGENT_PID=10691; export SSH2_AGENT_PID;
echo Agent pid 10691;

(これにより、エージェントの実行が開始されますが、この出力をシェルプロンプトにコピーアンドペーストしない限り、実際に使用することはできません。)

bash$ ssh-agent -c
setenv SSH2_AUTH_SOCK /tmp/ssh-era/ssh2-10751-agent;
setenv SSH2_AGENT_PID 10752;
echo Agent pid 10752;

(ご覧のとおり、cshtcshは変数を設定するためにsetenvを使用します。)

あなた自身のプログラムもこれを行うことができます。

bash$ foo=$(makefoo)

あなたのmakefooスクリプトは単に値を計算して出力し、それを呼び出し元に望むようにさせます - それを変数に割り当てることは一般的なユースケースですが、おそらくあなたがそれを生成するツールにハードコードしたいものではありません値。

0
tripleee

それは私が未解決と呼ぶものではありませんが、それでもシェルからスクリプトを呼び出す必要がある場合にも機能します。これは良い解決策ではありませんが、単一の静的環境変数に対しては十分に機能します。

1.)0(成功)または1(失敗)のいずれかで終了する条件でスクリプトを作成します。

if [[ $foo == "True" ]]; then
    exit 0
else
    exit 1

2.)終了コードに依存する別名を作成してください。

alias='myscript.sh && export MyVariable'

エイリアスを呼び出すと、スクリプトが呼び出され、条件が評価されます。親シェルで環境変数を設定するには、&&を使用してゼロを終了する必要があります。

これは漂流物ですが、それはピンチで役に立つことができます。

0
user1802263