web-dev-qa-db-de.com

Kann ein Shell-Skript Umgebungsvariablen der aufrufenden Shell setzen?

Ich versuche, ein Shell-Skript zu schreiben, das bei seiner Ausführung einige Umgebungsvariablen setzt, die in der Shell des Aufrufers gesetzt bleiben.

setenv FOO foo

in csh/tcsh oder

export FOO=foo

in sh/bash nur während der Ausführung des Skripts gesetzt.

Ich weiß schon, dass 

source myscript

führt die Befehle des Skripts aus, anstatt eine neue Shell zu starten. Dies kann dazu führen, dass die Umgebung des "Aufrufers" festgelegt wird.

Aber hier ist der Haken:

Ich möchte, dass dieses Skript von bash oder csh aufgerufen werden kann. Mit anderen Worten, ich möchte, dass Benutzer beider Shell mein Skript ausführen und die Umgebung ihrer Shell ändern können. 'Source' funktioniert also nicht für mich, da ein Benutzer, der csh ausführt, kein bash-Skript und ein Benutzer, der bash ausführt, kein csh-Skript erstellen kann.

Gibt es eine vernünftige Lösung, bei der ZWEI Versionen nicht in das Skript geschrieben und verwaltet werden müssen?

360
Larry Gritz

Ihr Shell-Prozess verfügt über eine Kopie der Umgebung des übergeordneten Elements und hat keinerlei Zugriff auf die Umgebung des übergeordneten Prozesses. Wenn Ihr Shell-Prozess beendet ist, gehen alle Änderungen verloren, die Sie an der Umgebung vorgenommen haben. Die Beschaffung einer Skriptdatei ist die am häufigsten verwendete Methode zum Konfigurieren einer Shell-Umgebung. Vielleicht möchten Sie einfach nur den Punkt beißen und eine für jede der beiden Shell-Varianten beibehalten.

237
converter42

Verwenden Sie die Aufrufsyntax "dot space script". Beispiel: So verwenden Sie den vollständigen Pfad zu einem Skript:

. /path/to/set_env_vars.sh

Und so gehen Sie vor, wenn Sie sich im selben Verzeichnis wie das Skript befinden:

. set_env_vars.sh

Diese führen das Skript unter der aktuellen Shell aus, anstatt eine andere zu laden (was bei ./set_env_vars.sh der Fall wäre). Da sie in derselben Shell ausgeführt wird, sind die von Ihnen festgelegten Umgebungsvariablen verfügbar, wenn sie beendet werden. 

Dies ist dasselbe wie beim Aufruf von source set_env_vars.sh, aber es ist kürzer zu tippen und funktioniert an einigen Stellen, an denen source nicht funktioniert. 

251
Humberto Romero

Sie können die Shell des Aufrufers nicht ändern, da sie sich in einem anderen Prozesskontext befindet. Wenn untergeordnete Prozesse die Variablen Ihrer Shell erben, erben sie die Kopien selbst

Sie können ein Skript schreiben, das die korrekten Befehle für tcsh Oder sh ausgibt, wie es aufgerufen wird. Wenn das Skript "setit" ist, machen Sie folgendes:

ln -s setit setit-sh

und

ln -s setit setit-csh

Jetzt entweder direkt oder in einem Alias, machst du das von sh

eval `setit-sh`

oder das von csh

eval `setit-csh`

setit verwendet $ 0, um den Ausgabestil zu bestimmen.

Dies erinnert daran, wie die Benutzer verwenden, um die Umgebungsvariable TERM zu erhalten.

Der Vorteil hierbei ist, dass setit nur in der gewünschten Shell geschrieben wird:

#!/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

mit den oben angegebenen symbolischen Links und der Auswertung des zurückgesetzten Anführungszeichens hat dies das gewünschte Ergebnis.

So vereinfachen Sie den Aufruf von csh, tcsh oder ähnlichen Shells:

alias dosetit 'eval `setit-csh`'

oder für sh, bash und dergleichen:

alias dosetit='eval `setit-sh`'

Eine schöne Sache dabei ist, dass Sie die Liste nur an einer Stelle verwalten müssen. Sie könnten die Liste sogar in eine Datei schreiben und cat nvpairfilename zwischen "in" und "do" setzen.

Auf diese Weise wurden die Einstellungen der Login-Shell-Terminals so ziemlich durchgeführt: Ein Skript würde Statements ausgeben, die in der Login-Shell ausgeführt werden sollen. Im Allgemeinen wird ein Alias ​​verwendet, um das Aufrufen zu vereinfachen, wie in "tset vt100". Wie bereits in einer anderen Antwort erwähnt, gibt es auch ähnliche Funktionen im INN UseNet-News-Server.

53
Thomas Kammeyer

In meinem .bash_profil habe ich:

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

Wenn ich also den Proxy deaktivieren möchte, laufen die Funktionen in der Login-Shell. 

44
chris

Es ist "irgendwie" durch die Verwendung von gdb und setenv (3) möglich, obwohl es mir schwer fällt, dies tatsächlich zu empfehlen. (Das heißt, das neueste Ubuntu lässt Sie dies nicht wirklich tun, ohne dem Kernel mitzuteilen, dass er mehr ptrace zulässt, und dasselbe gilt möglicherweise auch für andere Distributionen).

$ 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

Dies funktioniert - es ist nicht das, was ich verwenden würde, aber es "funktioniert". Lassen Sie uns ein Skript Teredo erstellen, um die Umgebungsvariable Teredo_WORMS festzulegen:

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

Es wird von der Korn-Shell interpretiert, exportiert die Umgebungsvariable und ersetzt sich dann durch eine neue interaktive Shell.

Vor dem Ausführen dieses Skripts haben wir Shell in der Umgebung auf die C-Shell gesetzt, und die Umgebungsvariable Teredo_WORMS ist nicht gesetzt:

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

Wenn das Skript ausgeführt wird, befinden Sie sich in einer neuen Shell, einer anderen interaktiven C-Shell, aber die Umgebungsvariable ist festgelegt:

% Teredo
% env | grep Teredo
TEREDO_WORMS=ukelele
%

Wenn Sie diese Shell verlassen, übernimmt die ursprüngliche Shell:

% exit
% env | grep Teredo
%

Die Umgebungsvariable ist in der ursprünglichen Shell-Umgebung nicht festgelegt. Wenn Sie zum Ausführen des Befehls exec Teredo verwenden, wird die ursprüngliche interaktive Shell durch die Korn-Shell ersetzt, die die Umgebung festlegt, und anschließend durch eine neue interaktive C-Shell:

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

Wenn Sie exit (oder Control-D), dann wird Ihre Shell beendet und Sie werden wahrscheinlich von diesem Fenster abgemeldet, oder Sie kehren zur vorherigen Shell-Ebene zurück, von der aus die Experimente gestartet wurden.

Der gleiche Mechanismus funktioniert für Bash oder Korn Shell. Möglicherweise stellen Sie fest, dass die Eingabeaufforderung nach den Exit-Befehlen an lustigen Stellen angezeigt wird.


Beachten Sie die Diskussion in den Kommentaren. Dies ist keine Lösung, die ich empfehlen würde, aber sie erfüllt den angegebenen Zweck eines einzelnen Skripts zum Festlegen der Umgebung, die mit allen Shells funktioniert (die die Option -i akzeptieren, um eine interaktive Shell zu erstellen). Sie können auch "[email protected]" nach der Option zum Weiterleiten anderer Argumente hinzufügen, wodurch die Shell möglicherweise als allgemeines Tool zum Festlegen der Umgebung und Ausführen von Befehlen verwendet werden kann. Möglicherweise möchten Sie den -i weglassen, wenn andere Argumente vorliegen, die zu Folgendem führen:

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

Das "${@-'-i'}"-Bit bedeutet: 'Wenn die Argumentliste mindestens ein Argument enthält, verwenden Sie die ursprüngliche Argumentliste. Andernfalls ersetzen Sie die nicht vorhandenen Argumente durch -i.

12

Sie sollten Module verwenden, siehe http://modules.sourceforge.net/

BEARBEITEN: Das Modulpaket wurde seit 2012 nicht aktualisiert, funktioniert aber für die Grundlagen noch einwandfrei. Alle neuen Funktionen, Glocken und Trillerpfeifen finden heute in lmod statt (was mir mehr gefällt): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

10
Davide

Eine weitere Problemumgehung, die ich nicht erwähnt sehe, ist das Schreiben des Variablenwerts in eine Datei.

Ich stieß auf ein sehr ähnliches Problem, bei dem ich den letzten Satztest ausführen wollte (anstelle aller Tests). Mein erster Plan war, einen Befehl zum Festlegen der Umgebungsvariablen TESTCASE zu schreiben und dann einen anderen Befehl zu haben, der diesen Befehl zum Ausführen des Tests verwendet. Unnötig zu sagen, dass ich genau das gleiche Problem hatte wie Sie.

Dann kam mir dieser einfache Hack:

Erster Befehl (testset):

#!/bin/bash

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

Zweiter Befehl (testrun):

#!/bin/bash

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

Fügen Sie die Markierung -l oben in Ihr Bash-Skript ein, d. H.

#!/usr/bin/env bash -l

...

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

Die Werte mit NAME1 und NAME2 wurden jetzt in Ihre aktuelle Umgebung exportiert. Diese Änderungen sind jedoch nicht dauerhaft. Wenn Sie möchten, dass sie dauerhaft sind, müssen Sie sie Ihrer .bashrc-Datei oder einer anderen init-Datei hinzufügen. 

Aus den Manpages:

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

Sie können den untergeordneten Prozess anweisen, seine Umgebungsvariablen zu drucken (durch Aufrufen von "env"), dann die gedruckten Umgebungsvariablen im übergeordneten Prozess durchlaufen und "export" für diese Variablen aufrufen.

Der folgende Code basiert auf Capturing-Ausgabe von find. -print0 in ein Bash-Array

Wenn die übergeordnete Shell die Bash ist, können Sie verwenden

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

Wenn die übergeordnete Shell der Bindestrich ist, stellt read das Flag -d nicht bereit und der Code wird komplizierter

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

Sie können eine andere Bash mit dem verschiedenen bash_profile aufrufen. Sie können auch ein spezielles bash_profile für die Verwendung in einer Umgebung mit mehreren Bashprofilen erstellen. 

Denken Sie daran, dass Sie functions innerhalb von bashprofile verwenden können und dass Funktionen global verfügbar sein können Zum Beispiel kann "function user {export USER_NAME $ 1}" eine Variable in Runtime setzen, z | Grep Olegchir

2
Oleg Chirukhin

Ich habe das vor vielen Jahren gemacht. Wenn ich mich recht erinnere, habe ich in jedem von .bashrc und .cshrc einen Alias ​​mit Parametern eingefügt, die die jeweiligen Formen des Umstellens der Umgebung auf eine gemeinsame Form mit Aliasing versehen.

Dann hat das Skript, das Sie in eine der beiden Shells laden, einen Befehl mit der letzten Form, die in jeder Shell als Alias ​​geeignet ist.

Wenn ich die konkreten Aliase finde, werde ich sie posten.

1
sancho.s

Die kurze Antwort lautet nein. Sie können die Umgebung des übergeordneten Prozesses nicht ändern. Es scheint jedoch, als wollten Sie eine Umgebung mit benutzerdefinierten Umgebungsvariablen und der vom Benutzer ausgewählten Shell.

Warum also nicht einfach so etwas

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

Wenn Sie mit der Umgebung fertig sind, exit.

1
Andrew

Ich habe eine Lösung mit Pipes, Eval und Signal erstellt.

parent() {
    if [ -z "$G_EVAL_FD" ]; then
            die 1 "Rode primeiro parent_setup no processo pai"
    fi
    if [ $(ppid) = "$$" ]; then
            "[email protected]"
    else
            kill -SIGUSR1 $$
            echo "[email protected]">&$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

Es könnte mit jedem Befehl funktionieren.

Unter OS X-Bash können Sie Folgendes tun:
Erstellen Sie die Bash-Skriptdatei, um die Variable aufzuheben

#!/bin/bash
unset http_proxy

Machen Sie die Datei ausführbar

Sudo chmod 744 unsetvar

Erstellen Sie einen Alias

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

Es sollte einsatzbereit sein, solange der Ordner mit der Skriptdatei an den Pfad angehängt ist.

1
Marton Tatai

Sie könnten immer Aliase verwenden

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

Technisch ist das richtig - nur "eval" greift keine andere Shell an. Aus der Sicht der Anwendung, die Sie in der modifizierten Umgebung ausführen möchten, ist der Unterschied jedoch null: Das Kind erbt die Umgebung des übergeordneten Elements, sodass die (modifizierte) Umgebung an alle absteigenden Prozesse übermittelt wird.

Faktisch bleibt die geänderte Umgebungsvariable - solange Sie unter dem übergeordneten Programm/Shell laufen.

Wenn die Umgebungsvariable nach dem Verlassen des übergeordneten Elements (Perl oder Shell) unbedingt beibehalten werden muss, muss das übergeordnete Shell die schwere Aufhebung durchführen. Eine Methode, die ich in der Dokumentation gesehen habe, besteht darin, dass das aktuelle Skript eine ausführbare Datei mit der erforderlichen "Export" -Sprache erzeugt und dann die übergeordnete Shell dazu bringt, sie auszuführen. Dabei ist stets zu berücksichtigen, dass Sie die Voranstellung ausführen müssen Befehl mit 'source', wenn Sie versuchen, eine nichtflüchtige Version der geänderten Umgebung hinter sich zu lassen. Ein Kluge am besten.

Die zweite Methode besteht darin, das Skript, das die Shell-Umgebung (.bashrc oder was auch immer) initiiert, so zu ändern, dass es den geänderten Parameter enthält. Dies kann gefährlich sein - wenn Sie das Initialisierungsskript zusammenfassen, kann Ihre Shell beim nächsten Start möglicherweise nicht mehr verfügbar sein. Es gibt viele Werkzeuge zum Ändern der aktuellen Shell. Durch das Anbringen der erforderlichen Anpassungen an den 'Launcher' können Sie diese Änderungen auch effektiv vorantreiben. __ Im Allgemeinen keine gute Idee; Wenn Sie nur die Umgebungsänderungen für eine bestimmte Anwendungssuite benötigen, müssen Sie das Shell-Startskript anschließend wieder in den ursprünglichen Zustand (mit vi oder was auch immer) zurücksetzen.

Kurz gesagt, es gibt keine guten (und einfachen) Methoden. Vermutlich wurde dies erschwert, um sicherzustellen, dass die Sicherheit des Systems nicht unwiderruflich beeinträchtigt wird.

1
David Lovering

Eine andere Option ist die Verwendung von "Umgebungsmodulen" ( http://modules.sourceforge.net/ ). Dies führt leider eine dritte Sprache in den Mix ein. Sie definieren die Umgebung mit der Sprache von Tcl, es gibt jedoch einige praktische Befehle für typische Modifikationen (pre-pend vs append vs set). Außerdem müssen Umgebungsmodule installiert sein. Sie können dann mit module load *XXX* die gewünschte Umgebung benennen. Der Modulbefehl ist im Grunde ein ausgefallener Alias ​​für den oben von Thomas Kammeyer beschriebenen eval-Mechanismus. Der Hauptvorteil hier ist, dass Sie die Umgebung in einer Sprache erhalten und sich auf "Umgebungsmodule" verlassen können, um sie in sh, ksh, bash, csh, tcsh, zsh, Python (?!? !!) usw. zu übersetzen.

1
Howard Hobbes

Ich sehe keine Antwort darauf, wie man dieses Problem mit kooperierenden Prozessen umgehen kann. Ein übliches Muster für Dinge wie ssh-agent ist, dass der untergeordnete Prozess einen Ausdruck druckt, den die übergeordneten eval ändern können.

bash$ eval $(shh-agent)

Beispielsweise hat ssh-agent Optionen zur Auswahl der Csh- oder Bourne-kompatiblen Ausgabesyntax.

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;

(Dadurch wird der Agent gestartet, kann jedoch nicht verwendet werden, es sei denn, Sie kopieren diese Ausgabe nun in Ihre Shell-Eingabeaufforderung.)

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

(Wie Sie sehen können, verwenden csh und tcshsetenv, um Variablen einzustellen.)

Ihr eigenes Programm kann das auch. 

bash$ foo=$(makefoo)

Ihr makefoo-Skript berechnet und druckt den Wert einfach und lässt den Aufrufer damit tun, was er will - seine Zuweisung zu einer Variablen ist ein häufiger Anwendungsfall, aber wahrscheinlich nicht etwas, das Sie in das Tool, das den Code erzeugt, hartcodieren möchten Wert.

0
tripleee

Ich würde es nicht hervorragend nennen, aber das funktioniert auch, wenn Sie das Skript ohnehin von der Shell aus aufrufen müssen. Dies ist keine gute Lösung, aber für eine einzelne statische Umgebungsvariable funktioniert es gut genug. 

1.) Erstellen Sie ein Skript mit einer Bedingung, die entweder 0 (erfolgreich) oder 1 (nicht erfolgreich) beendet.

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

2.) Erstellen Sie einen Alias, der vom Exit-Code abhängig ist.

alias='myscript.sh && export MyVariable'

Sie rufen den Alias ​​auf, der das Skript aufruft, das die Bedingung auswertet, die zum Beenden von Null über '&&' erforderlich ist, um die Umgebungsvariable in der übergeordneten Shell festzulegen. 

Dies ist Treibgut, aber es kann hilfreich sein.

0
user1802263