web-dev-qa-db-fra.com

Un script Shell peut-il définir les variables d’environnement du Shell appelant?

J'essaie d'écrire un script Shell qui, lorsqu'il sera exécuté, définira des variables d'environnement qui resteront définies dans le shell de l'appelant.

setenv FOO foo

en csh/tcsh, ou

export FOO=foo

dans sh/bash, définissez-le uniquement lors de l'exécution du script.

Je sais déjà que 

source myscript

exécutera les commandes du script plutôt que de lancer un nouveau shell, ce qui peut entraîner la définition de l'environnement de "l'appelant".

Mais voici le hic:

Je veux que ce script puisse être appelé depuis bash ou csh. En d'autres termes, je souhaite que les utilisateurs de l'un ou l'autre shell puissent exécuter mon script et que l'environnement de leur shell soit modifié. Donc, 'source' ne fonctionnera pas pour moi, car un utilisateur exécutant csh ne peut pas créer un script bash, et un utilisateur exécutant bash ne peut pas créer un script csh.

Existe-t-il une solution raisonnable ne nécessitant pas l’écriture et la maintenance de deux versions du script?

360
Larry Gritz

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, toutes les modifications apportées à son environnement sont perdues. La recherche d'un fichier de script est la méthode la plus couramment utilisée pour configurer un environnement Shell. Vous voudrez peut-être simplement mordre la balle et en conserver une pour chacun des deux types de Shell.

237
converter42

Utilisez la syntaxe d'appel "script espace par points". Par exemple, voici comment procéder en utilisant le chemin complet du script:

. /path/to/set_env_vars.sh

Et voici comment procéder si vous êtes dans le même répertoire que le script:

. set_env_vars.sh

Celles-ci exécutent le script sous le shell actuel au lieu d'en charger un autre (ce qui se produirait si vous exécutiez ./set_env_vars.sh). Étant donné qu'il s'exécute dans le même shell, les variables d'environnement que vous avez définies seront disponibles lors de sa fermeture. 

C’est la même chose que d’appeler source set_env_vars.sh, mais elle est plus courte à taper et pourrait fonctionner dans certains endroits où source ne fonctionne pas. 

251
Humberto Romero

Vous ne pourrez pas modifier le shell de l'appelant car il se trouve dans un contexte de processus différent. Lorsque les processus enfants héritent des variables de votre shell, ils héritent eux-mêmes des copies.

Une chose que vous pouvez faire est d'écrire un script qui émet les commandes correctes pour tcsh .__ ou sh, en fonction de la manière dont il est appelé. Si votre script est "setit" alors faites:

ln -s setit setit-sh

et

ln -s setit setit-csh

Maintenant, directement ou dans un alias, vous le faites à partir de sh

eval `setit-sh`

ou ceci 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 jeu de variables d’environnement TERM.

L'avantage ici est que setit est écrit dans le shell de votre choix, comme dans:

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

avec les liens symboliques donnés ci-dessus et l’évaluation de l’expression cités en arrière, cela donne le résultat souhaité.

Pour simplifier l’appel de shells csh, tcsh ou similaires:

alias dosetit 'eval `setit-csh`'

ou pour sh, bash, etc.:

alias dosetit='eval `setit-sh`'

Une bonne chose à ce propos est qu’il n’est nécessaire de conserver la liste qu’à un seul endroit . En théorie, vous pouvez même la coller dans un fichier et placer cat nvpairfilename entre "in" et "do".

C’est à peu près la façon dont les paramètres de terminal du shell de connexion étaient utilisés: un script générerait les états à exécuter dans le shell de connexion. Un alias serait généralement utilisé pour simplifier l'appel, 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.

53
Thomas Kammeyer

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
}

Ainsi, lorsque je souhaite désactiver le proxy, les fonctions S'exécutent dans le shell de connexion et définissent les variablesaspectées et souhaitées. 

44
chris

C'est "un peu" possible en utilisant gdb et setenv (3) , bien que j'ai du mal à vous recommander de le faire. (En outre, l’ubuntu le plus récent ne vous laissera pas faire cela sans dire au noyau d’être plus permissif à propos de ptrace, et il en ira de même pour d’autres distributions).

$ 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
Kjetil Joergensen

Cela fonctionne - ce n'est pas ce que j'utiliserais, mais cela "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 shell Korn, exporte la variable d'environnement, puis se remplace par un nouveau shell interactif.

Avant d'exécuter ce script, nous avons défini Shell dans l'environnement du C Shell, et la variable d'environnement Teredo_WORMS n'est pas définie:

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

Lorsque le script est exécuté, vous vous trouvez 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 quittez ce shell, le shell d'origine prend le relais:

% exit
% env | grep Teredo
%

La variable d'environnement n'est pas définie dans l'environnement du shell d'origine. Si vous utilisez exec Teredo pour exécuter la commande, le shell interactif d'origine est remplacé par le shell Korn qui définit l'environnement, puis celui-ci est remplacé par un nouveau shell C interactif:

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

Si vous tapez exit (ou Control-D), puis votre Shell quitte, vous déconnectant probablement de cette fenêtre ou vous ramenant au niveau précédent de Shell à partir duquel les expériences ont commencé.

Le même mécanisme fonctionne pour Bash ou Korn Shell. Vous constaterez peut-être que l'invite après les commandes de sortie apparaît dans des endroits amusants.


Notez la discussion dans les commentaires. Ce n’est pas une solution que je recommanderais, mais il permet de réaliser l’environnement qui fonctionne avec tous les shells (qui accepte l’option -i pour créer un shell interactif). Vous pouvez également ajouter "$@" après l'option permettant de relayer tous les autres arguments, ce qui pourrait ensuite rendre le shell utilisable en tant qu'outil général 'set environment and execute command'. Vous voudrez peut-être omettre le -i s'il existe d'autres arguments, ce qui conduit à:

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

Le "${@-'-i'}" bit signifie 'si la liste d'arguments contient au moins un argument, utilisez la liste d'arguments d'origine; sinon, remplacez -i par les arguments non existants.

12
Jonathan Leffler

Vous devriez utiliser des modules, voir http://modules.sourceforge.net/

EDIT: Le package 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 arrivent aujourd'hui à Imod (ce qui me plait davantage): https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

10
Davide

Une autre solution de contournement que je ne vois pas mentionnée consiste à écrire la valeur de la variable dans un fichier.

J'ai rencontré un problème très similaire dans lequel je voulais pouvoir exécuter le dernier test d'ensemble (au lieu de tous mes tests). Mon premier plan consistait à écrire une commande pour définir la variable env TESTCASE, puis à utiliser une autre commande qui l'utiliserait pour exécuter le test. Inutile de dire que j'avais exactement le même problème que vous.

Mais alors je suis venu avec ce hack simple:

Première commande (testset):

#!/bin/bash

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

Deuxième commande (testrun):

#!/bin/bash

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

Ajoutez 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 auront maintenant été exportées vers votre environnement actuel, mais ces modifications ne sont pas permanentes. Si vous souhaitez qu’ils soient permanents, vous devez les ajouter à votre fichier .bashrc ou à un autre fichier init. 

À partir des pages de manuel:

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

Vous pouvez indiquer au processus enfant d’imprimer ses variables d’environnement (en appelant "env"), puis de parcourir les variables d’environnement imprimées dans le processus parent et d’appeler "export" pour ces variables.

Le code suivant est basé sur Capture de la sortie de find. -print0 dans un tableau bash

Si le shell parent est le bash, vous pouvez utiliser

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

Si le shell parent est le tiret, read ne fournit pas l'indicateur -d et le code devient plus compliqué.

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

Vous pouvez appeler un autre Bash avec le différent bash_profile. En outre, vous pouvez créer un fichier bash_profile spécial à utiliser dans un environnement multi-bashprofile. 

Rappelez-vous que vous pouvez utiliser functions à l'intérieur de bashprofile, et que les fonctions seront globalement disponibles . Par exemple, "function user {export USER_NAME $ 1}" peut définir une variable à l'exécution, par exemple: utilisateur olegchir && env | grep olegchir

2
Oleg Chirukhin

Je l'ai fait il y a plusieurs années. Si je me rappelle bien, j’ai inclus un alias dans chacun des fichiers .bashrc et .cshrc, avec des paramètres, aliasant les formes respectives de configuration de l’environnement en une forme commune.

Ensuite, le script que vous allez rechercher dans l’un des deux shells a une commande avec cette dernière forme, qui est un alias approprié dans chaque shell.

Si je trouve les pseudonymes concrets, je les posterai.

1
sancho.s

La réponse courte est non, vous ne pouvez pas modifier l'environnement du processus parent, mais il semble que vous souhaitiez un environnement avec des variables d'environnement personnalisées et le shell que l'utilisateur a choisi.

Alors pourquoi ne pas simplement quelque chose comme

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

Ensuite, lorsque vous avez terminé avec l’environnement, il suffit de exit.

1
Andrew

J'ai créé une solution à l'aide de 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

Cela peut fonctionner avec n'importe quelle commande.

Sous OS X bash, vous pouvez effectuer les opérations suivantes:
Créer le fichier de script bash pour supprimer 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 à être utilisé aussi longtemps que le dossier contenant votre fichier de script est ajouté au chemin.

1
Marton Tatai

Vous pouvez toujours utiliser des alias

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

Techniquement, c'est exact: seul «eval» ne crée pas un autre shell. Toutefois, 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; l'environnement (modifié) est donc transmis à tous les processus descendants.

Ipso facto, la variable d'environnement modifiée «colle» - tant que vous exécutez le programme parent/Shell.

S'il est absolument nécessaire que la variable d'environnement reste après la sortie du parent (Perl ou Shell), il est nécessaire que le shell parent effectue le gros travail. Une méthode que j'ai vue dans la documentation consiste pour le script actuel à générer un fichier exécutable avec le langage "d'exportation" nécessaire, puis à inciter le shell parent à l'exécuter - en étant toujours conscient du fait qu'il est nécessaire de préfacer le fichier. commande avec 'source' si vous essayez de laisser une version non volatile de l'environnement modifié. Un Kluge au mieux.

La deuxième méthode consiste à modifier le script qui lance l'environnement Shell (.bashrc ou autre) pour qu'il contienne le paramètre modifié. Cela peut être dangereux: si vous lancez le script d'initialisation, votre shell risque de ne plus être disponible lors de son prochain lancement. Il existe de nombreux outils pour modifier le shell actuel; en apposant les modifications nécessaires sur le "lanceur", vous poussez efficacement ces changements également .. Généralement, ce n'est pas une bonne idée; si vous n'avez besoin que des modifications de l'environnement pour une suite d'applications particulière, vous devrez revenir en arrière et ramener le script de lancement du shell à son état initial (à l'aide de vi ou autre) par la suite.

En bref, il n'y a pas de bonne (et facile) méthode. Vraisemblablement, il a été difficile d’assurer que la sécurité du système ne soit pas compromise de manière irrévocable.

1
David Lovering

Une autre option consiste à utiliser "Modules d’environnement" ( http://modules.sourceforge.net/ ). Cela introduit malheureusement une troisième langue dans le mix. Vous définissez l'environnement avec le langage de Tcl, mais il existe quelques commandes pratiques pour les modifications typiques (prepend vs append vs set). Vous devrez également avoir installé des modules d’environnement. Vous pouvez ensuite utiliser module load *XXX* pour nommer l'environnement souhaité. La commande de module est fondamentalement un alias 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 vous fier à des "modules d'environnement" pour le traduire en sh, ksh, bash, csh, tcsh, zsh, python (?!? !!), etc.

1
Howard Hobbes

Je ne vois aucune réponse expliquant comment résoudre ce problème avec des processus coopérants. Un modèle commun avec des éléments tels que ssh-agent consiste à faire en sorte que le processus enfant imprime 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 avec 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;

(Ceci provoque l'exécution de l'agent, mais ne vous autorise pas à l'utiliser, à moins que vous ne colliez maintenant cette sortie dans votre invite du shell.) Comparez:

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 constater, csh et tcsh utilisent setenv pour définir des variables.)

Votre propre programme peut le faire aussi. 

bash$ foo=$(makefoo)

Votre script makefoo calculera et imprimera simplement la valeur et laissera l’appelant en faire tout ce qu’il voudra - l’affecter à une variable est un cas d’utilisation courant, mais ce n’est probablement pas quelque chose que vous souhaitez coder en dur dans l’outil qui produit le fichier. valeur.

0
tripleee

Ce n'est pas ce que j'appellerais exceptionnel, mais cela fonctionne aussi si vous devez quand même appeler le script depuis le shell. 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 quitte 0 (réussi) ou 1 (non réussi)

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

2.) Créez un alias qui dépend du code de sortie.

alias='myscript.sh && export MyVariable'

Vous appelez l'alias, qui appelle le script, qui évalue la condition, qui est nécessaire pour quitter zéro via '&&' afin de définir la variable d'environnement dans le Shell parent. 

C'est flotsam, mais cela peut être utile dans un pincement.

0
user1802263