Un script shell peut-il définir les variables d'environnement du shell appelant?
j'essaie d'écrire un script shell qui, une fois lancé, définira des variables d'environnement qui resteront définies dans l'interpréteur de commandes de l'appelant.
setenv FOO foo
dans csh/ tcsh, ou
export FOO=foo
dans sh/bash ne le met que pendant l'exécution du script.
je sais déjà que
source myscript
exécutera les commandes du script plutôt que de lancer un nouvel interpréteur de commandes, ce qui peut avoir pour résultat "l'appelant" environnement.
Mais voici le hic:
je veux que ce script soit callable depuis bash ou csh. En d'autres termes, je veux que les utilisateurs de l'un ou l'autre shell puissent exécuter mon script et faire modifier l'environnement de leur shell. Donc 'source' ne fonctionnera pas pour moi, puisqu'un utilisateur qui exécute csh ne peut pas source un script bash, et un utilisateur qui exécute bash ne peut pas source un script csh.
y a-t-il une solution raisonnable qui n'implique pas d'avoir pour écrire et maintenir deux versions sur le script?
21 réponses
votre processus shell a une copie de l'environnement du parent et aucun accès à l'environnement du processus parent. Lorsque votre processus shell se termine, tous les changements que vous avez apportés à son environnement sont perdus. Sourcing un fichier de script est la méthode la plus couramment utilisée pour configurer un environnement shell, vous pouvez juste vouloir mordre la balle et maintenir un pour chacune des deux saveurs de shell.
utilisez la syntaxe d'appel" point Space script". Par exemple, voici comment le faire en utilisant le chemin complet vers un script:
. /path/to/set_env_vars.sh
Et voici comment le faire si vous êtes dans le même répertoire que le script:
. set_env_vars.sh
ceux-ci exécutent le script sous le shell courant au lieu d'en charger un autre (ce qui se produirait si vous faisiez ./set_env_vars.sh
). Parce qu'il fonctionne dans le même shell, les variables d'environnement vous sera disponible quand il sort.
c'est la même chose que d'appeler source set_env_vars.sh
, mais c'est plus court pour taper et pourrait fonctionner dans certains endroits où source
ne fonctionne pas.
vous n'allez pas pouvoir modifier le shell de l'appelant parce que c'est dans un contexte de processus différent. Quand les processus enfant héritent les variables de votre shell, ils sont hériter des copies elles-mêmes.
Une chose que vous pouvez faire est d'écrire un script qui émet les commandes appropriées pour tcsh ou sh basé sur la façon dont il est invoqué. Si vous êtes script est "setit" puis faire:
ln -s setit setit-sh
et
ln -s setit setit-csh
maintenant soit directement soit dans un alias, vous faites ceci de sh
eval `setit-sh`
ou ce de csh
eval `setit-csh`
setit utilise 0 $ pour déterminer son style de sortie.
cela rappelle la façon dont les gens utilisent pour obtenir le terme environnement variable ensemble.
L'avantage ici est que setit est juste écrit dans n'importe quel shell comme dans:
#!/bin/bash
arg0="151940920"
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
avec les liens symboliques donnés ci-dessus, et l'éval expression en backquoted, cela a le résultat désiré.
pour simplifier l'invocation pour CSH, tcsh, ou coquilles similaires:
alias dosetit 'eval `setit-csh`'
ou pour sh, bash, et similaires:
alias dosetit='eval `setit-sh`'
une bonne chose à ce sujet est que vous avez seulement à maintenir la liste en un seul endroit.
En théorie, vous pouvez même coller la liste dans un fichier et mettre cat nvpairfilename
entre "in" et "do".
C'est à peu près la façon dont le shell de login les réglages du terminal étaient faits: un script produirait des États à exécuter dans le shell de connexion. Un alias serait généralement utilisé pour rendre l'invocation simple, comme dans "tset vt100". Comme mentionné dans une autre réponse, il existe également des fonctionnalités similaires dans le serveur de nouvelles INN UseNet.
Dans mon .bash_profile j'ai :
# 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
}
donc quand je veux désactiver le mandataire, la fonction(s) à exécuter dans le shell de connexion et définit les variables comme prévu et voulu.
c'est" en quelque sorte "possible grâce à l'utilisation de gdb et setenv(3) , bien que j'ai du mal à recommander réellement de faire cela. (De plus, c'est-à-dire que l'ubuntu le plus récent ne vous laissera pas réellement faire cela sans dire au noyau d'être plus permissif sur ptrace, et la même chose peut aller pour d'autres distros aussi bien).
$ cat setfoo
#! /bin/bash
gdb /proc/${PPID}/exe ${PPID} <<END >/dev/null
call setenv("foo", "bar", 0)
END
$ echo $foo
$ ./setfoo
$ echo $foo
bar
cela fonctionne - il ne est pas ce que je voudrais utiliser, mais il 'fonctionne'. Créons un script teredo
pour définir la variable d'environnement TEREDO_WORMS
:
#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL -i
il sera interprété par le Korn shell, exporte la variable d'environnement, puis se remplace par un nouveau shell interactif.
Avant d'exécuter ce script, nous avons SHELL
dans l'environnement du shell C, et la variable d'environnement TEREDO_WORMS
n'est pas défini:
% env | grep SHELL
SHELL=/bin/csh
% env | grep TEREDO
%
lorsque le script est lancé, vous êtes dans un nouveau shell, un autre shell C interactif, mais la variable d'environnement est définie:
% teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%
lorsque vous sortez de ce shell, le shell d'origine prend la relève:
% exit
% env | grep TEREDO
%
la variable d'environnement n'est pas définie dans l'environnement original de shell. Si vous utilisez exec teredo
pour exécuter la commande, le shell interactif est remplacé par le Korn shell définit l'environnement, puis celui-ci est remplacé à son tour par un nouveau shell C interactif:
% exec teredo
% env | grep TEREDO
TEREDO_WORMS=ukelele
%
si vous tapez exit
(ou Control-D ), alors votre shell sort, probablement en vous déconnectant de cette fenêtre, ou en vous ramenant au niveau précédent de shell d'où les expériences ont commencé.
le même mécanisme fonctionne pour la coque Bash ou Korn. Vous pouvez trouver que le prompt après les commandes exit apparaît dans de drôles d'endroits.
noter la discussion dans les commentaires. Ce n'est pas une solution que je recommanderais, mais elle atteint l'objectif déclaré d'un script unique pour définir l'environnement qui fonctionne avec tous les shells (qui acceptent l'option -i
pour faire un shell interactif). Vous pouvez également ajouter "$@"
après l'option de relais d'autres arguments, ce qui pourrait alors rendre le shell utilisable comme environnement de jeu général et exécuter commande de l'outil. Vous pourriez vouloir omettre le -i
s'il y a d'autres arguments, conduisant à:
#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL "${@-'-i'}"
le bit "${@-'-i'}"
signifie "Si la liste d'arguments contient au moins un argument, utiliser la liste d'arguments originale; sinon, remplacer -i
par -i
.
vous devez utiliser les modules, voir http://modules.sourceforge.net/
EDIT: le paquet de modules n'a pas été mis à jour depuis 2012 mais fonctionne toujours correctement pour les bases. Toutes les nouvelles fonctionnalités, cloches et sifflets se produisent dans lmod ce jour (que je l'aime plus): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod
une autre solution que je ne vois pas mentionnée est d'écrire la valeur de la variable dans un fichier.
j'ai rencontré un problème très similaire où je voulais pouvoir exécuter le test du dernier set (au lieu de tous mes tests). Mon premier plan était d'écrire une commande pour configurer la variable env TESTCASE, puis d'avoir une autre commande qui utiliserait ceci pour exécuter le test. Inutile de dire que j'avais exactement le même problème que vous.
mais j'ai trouvé ce simple hack:
Première commande ( testset
):
#!/bin/bash
if [ $# -eq 1 ]
then
echo > ~/.TESTCASE
echo "TESTCASE has been set to: "
else
echo "Come again?"
fi
Second commandement ( testrun
):
#!/bin/bash
TESTCASE=$(cat ~/.TESTCASE)
drush test-run $TESTCASE
ajouter le drapeau-l en haut de votre script bash i.e.
#!/usr/bin/env bash -l
...
export NAME1="VALUE1"
export NAME2="VALUE2"
les valeurs avec NAME1
et NAME2
seront maintenant exportées dans votre environnement actuel, mais ces changements ne sont pas permanents. Si vous voulez être permanente, vous devez les ajouter à votre .bashrc
fichier ou un autre fichier init.
De l'homme pages:
-l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
vous pouvez demander au processus enfant d'imprimer ses variables d'environnement (en appelant" env"), puis faire une boucle sur les variables d'environnement imprimées dans le processus parent et appeler" exporter " sur ces variables.
le code suivant est basé sur Capturing output of find . - print0 dans un tableau bash
si le shell parent est le bash, vous pouvez utiliser
while IFS= read -r -d $'"151900920"' line; do
export "$line"
done < <(bash -s <<< 'export VARNAME=something; env -0')
echo $VARNAME
si le shell parent est le tiret, alors read
ne fournit pas le drapeau-d et le code devient plus compliqué
TMPDIR=$(mktemp -d)
mkfifo $TMPDIR/fifo
(bash -s << "EOF"
export VARNAME=something
while IFS= read -r -d $'"151910920"' 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
vous pouvez invoquer un autre Bash avec le différent bash_profile. De plus, vous pouvez créer un bash_profile spécial pour une utilisation dans un environnement multi-bashprofile.
rappelez-vous que vous pouvez utiliser fonctions à l'intérieur de bashprofile, et que les fonctions seront disponibles globalement. par exemple, "fonction user { export USER_NAME $1}" peut définir une variable dans runtime, par exemple: user olegchir & & env / grep olegchir
techniquement, c'est correct -- seulement "eval" ne biffe pas un autre shell. Cependant, du point de vue de l'application que vous essayez d'exécuter dans l'environnement modifié, la différence est nulle: l'enfant hérite de l'environnement de son parent, de sorte que le (modifié) l'environnement est transmis à tous les processus descendant.
Ipso facto, la variable d'environnement modifiée 'sticks' -- aussi longtemps que vous exécutez sous le programme/shell parent.
S'il est absolument nécessaire que la variable d'environnement reste après que le parent (Perl ou shell) est sorti, il est nécessaire que le Parent shell fasse le levage lourd. Une méthode que j'ai vue dans la documentation est que le script courant lance un fichier exécutable avec le langage 'export' nécessaire, et ensuite fait en sorte que le shell parent l'exécute -- en étant toujours conscient du fait que vous devez préfacer la commande avec 'source' si vous essayez de quitter un version non volatile de l'environnement modifié derrière. Un Kluge au mieux.
la seconde méthode consiste à modifier le script qui initie l'environnement shell (.bashrc ou autre) pour contenir le paramètre modifié. Cela peut être dangereux -- si vous utilisez le script d'initialisation, il peut rendre votre shell indisponible la prochaine fois qu'il essaie de lancer. Il y a beaucoup d'outils pour modifier le shell actuel; en fixant les modifications nécessaires au "lanceur" vous effectivement pousser ces changements aussi. En général, ce n'est pas une bonne idée; si vous n'avez besoin que des changements d'environnement pour une suite d'application particulière, vous devrez revenir en arrière et retourner le script de lancement du shell à son état originel (en utilisant vi ou n'importe quoi d'autre) par la suite.
en bref, il n'y a pas de bonnes (et faciles) méthodes. Sans doute cela a été difficile d'assurer la sécurité du système n'a pas été irrévocablement compromis.
la réponse courte est non, vous ne pouvez pas modifier l'environnement du processus parent, mais il semble que ce que vous voulez est un environnement avec des variables d'environnement personnalisées et le shell que l'Utilisateur a choisi.
alors pourquoi pas quelque chose comme
#!/usr/bin/env bash
FOO=foo $SHELL
puis quand vous avez terminé avec l'environnement, juste exit
.
vous pouvez toujours utiliser des alias
alias your_env='source ~/scripts/your_env.sh'
une autre option consiste à utiliser "les Modules D'environnement" ( http://modules.sourceforge.net / ). Cela introduit malheureusement une troisième langue dans le mélange. Vous définissez L'environnement avec le langage Tcl, mais il y a quelques commandes pratiques pour les modifications typiques (prepend vs. append vs set). Vous devrez également installer des modules d'environnement. Vous pouvez alors utiliser module load *XXX*
pour nommer l'environnement que vous voulez. La commande module est essentiellement un pseudonyme de fantaisie pour le mécanisme eval
décrit ci-dessus par Thomas Kammeyer. Le principal avantage ici est que vous pouvez maintenir l'environnement dans une langue et compter sur "les Modules D'environnement" pour le traduire en sh, ksh, bash, csh, tcsh, zsh, python (?!?!!), etc.
Je l'ai fait il y a de nombreuses années. Si je me souviens bien, j'ai inclus un alias dans chacun .bashrc et .cshrc, avec paramètres, aliasing les formes respectives de réglage de l'environnement à une forme commune.
alors le script que vous allez source dans l'un des deux shells a une commande avec cette dernière forme, qui est aliasée appropriée dans chaque shell.
si je trouve les alias concrets, je les posterai.
j'ai créé une solution en utilisant des pipes, eval et 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
ça peut marcher avec n'importe quel ordre.
sous OS X bash vous pouvez faire ce qui suit:
Créer le fichier script bash pour désactiver la variable
#!/bin/bash
unset http_proxy
Rendre le fichier exécutable
sudo chmod 744 unsetvar
créer un alias
alias unsetvar='source /your/path/to/the/script/unsetvar'
il devrait être prêt à utiliser si longtemps que vous avez le dossier contenant votre fichier de script ajouté au chemin.
je ne vois pas de réponse à documenter la façon de contourner ce problème avec coopérant processus. Un modèle commun avec des choses comme ssh-agent
est d'avoir le processus enfant imprimer une expression que le parent peut eval
.
bash$ eval $(shh-agent)
par exemple, ssh-agent
a des options pour sélectionner la syntaxe de sortie compatible csh ou 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;
(cela provoque l'agent de commencer à courir, mais ne vous permet pas de réellement utilisez-le, sauf si vous copiez-collez cette sortie à votre invite shell.) Comparer:
bash$ ssh-agent -c
setenv SSH2_AUTH_SOCK /tmp/ssh-era/ssh2-10751-agent;
setenv SSH2_AGENT_PID 10752;
echo Agent pid 10752;
(comme vous pouvez le voir, csh
et tcsh
utilise setenv
pour mettre varibles.)
Votre propre programme peut faire cela aussi.
bash$ foo=$(makefoo)
votre script makefoo
calculerait et imprimerait simplement la valeur, et laisserait l'appelant faire ce qu'il veut avec elle -- l'assigner à une variable est un cas d'usage courant, mais probablement pas quelque chose que vous voulez coder en dur dans l'outil qui produit de la valeur.
ce n'est pas ce que j'appellerais outstanding, mais cela fonctionne aussi si vous avez besoin d'appeler le script depuis le shell de toute façon. Ce n'est pas une bonne solution, mais pour une seule variable d'environnement statique, cela fonctionne assez bien.
1.) Créer un script avec une condition qui sort soit 0 (réussi) soit 1 (pas réussi)
if [[ $foo == "True" ]]; then
exit 0
else
exit 1
2.) Créer un alias dépendant du code de sortie.
alias='myscript.sh && export MyVariable'
vous appelez le alias, qui appelle le script, qui évalue la condition, qui est requis pour quitter zéro via le '& & ' afin de définir la variable d'environnement dans le shell parent.
c'est flotsam, mais il peut être utile dans un pincement.
Autres que les écrits des conditionnelles en fonction de ce que $SHELL/$TERME est défini, non. Qu'y a-t-il de mal à utiliser Perl? C'est assez ubiquiste (Je ne peux pas penser à une seule variante UNIX qui ne l'a pas), et ça vous évitera les ennuis.