Может ли сценарий оболочки установить переменные среды вызывающей оболочки?


Я пытаюсь написать сценарий оболочки, который при запуске будет устанавливать некоторые переменные среды, которые будут оставаться установленными в оболочке вызывающего объекта.

setenv FOO foo

в csh/tcsh или

export FOO=foo

в sh / bash установите его только во время выполнения скрипта.

Я уже знаю, что

source myscript

будет запускать команды скрипта, а не запускать новую оболочку, и это может привести к настройке среды "вызывающего".

но вот руб.:

Я хочу, чтобы этот скрипт можно было вызвать из bash или csh. Другими словами, Я хочу, чтобы пользователи любой оболочки могли запускать мой скрипт и изменять среду своей оболочки. Так что "источник" не будет работать для меня, поскольку пользователь, работающий с хсг могут не источник bash-скрипт, и пользователь работает bash могут не источник сценарий хсг.

есть ли разумное решение, которое не включает в себя необходимость писать и поддерживать две версии сценария?

21 336

21 ответ:

ваш процесс оболочки имеет копию родительской среды и никакого доступа к среде родительского процесса вообще. Когда процесс оболочки завершается, все изменения, внесенные в его среду, теряются. Поиск файла сценария является наиболее часто используемым методом для настройки оболочки, вы можете просто хочу, чтобы стиснуть зубы и поддерживать для каждого из двух вариантах корпуса.

используйте синтаксис вызова "dot space script". Например, вот как это сделать, используя полный путь к скрипту:

. /path/to/set_env_vars.sh

и вот как это сделать, если вы находитесь в том же каталоге, что и скрипт:

. set_env_vars.sh

они выполняют скрипт под текущей оболочкой вместо загрузки другого (что и произошло бы, если бы вы сделали ./set_env_vars.sh). Поскольку он работает в той же оболочке, установленные переменные среды будут доступны при его выходе.

это то же самое как вызов source set_env_vars.sh, но он короче и может работать в некоторых местах, где source нет.

вы не сможете изменить оболочку вызывающего объекта, потому что она находится в другом контексте процесса. Когда дочерние процессы наследуют переменные оболочки, они наследование самих копий.

одна вещь вы можете сделать, это написать скрипт, который выдает правильные команды для tcsh или sh на основе того, как он вызывается. Если у вас скрипт "setit" то сделайте:

ln -s setit setit-sh

и

ln -s setit setit-csh

теперь либо непосредственно, либо в псевдониме, вы делаете это из ш

eval `setit-sh`

или это из csh

eval `setit-csh`

setit использует $0 для определения стиля вывода.

это напоминает о том, как люди используют, чтобы получить набор переменных среды термин.

преимущество здесь в том, что setit просто пишется в любой оболочке, которую вы любите, как в:

#!/bin/bash
arg0=
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`'

одна хорошая вещь об этом является то, что вы только должны поддерживать список в одном месте. Теоретически вы могли бы даже вставить список в файл и поставить cat nvpairfilename между "in"и " do".

это в значительной степени то, как настройки терминала оболочки входа в систему использовались: скрипт выводил бы статменты, которые будут выполняться в оболочке входа. Псевдоним, как правило, используется, чтобы сделать вызов просто, как в "цет вт100". Как упоминалось в другом ответе, существует также аналогичная функциональность в Inn UseNet news server.

в моем .файл у меня есть :

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

Так когда я хочу отключить прокси, функция(ы) запускается в оболочке входа и устанавливает переменные как и ожидалось и хотелось.

это" своего рода " возможно с помощью 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

это работает - это не то, что я бы использовал, но это "работает". Давайте создадим скрипт teredo для установки переменной окружения TEREDO_WORMS:

#!/bin/ksh
export TEREDO_WORMS=ukelele
exec $SHELL -i

он будет интерпретирован оболочкой Korn, экспортирует переменную среды, а затем заменяет себя новой интерактивной оболочкой.

перед запуском этого скрипта, у нас есть SHELL установите в среде оболочку C и переменную окружения TEREDO_WORMS не установлено:

% env | grep SHELL
SHELL=/bin/csh
% env | grep TEREDO
%

когда скрипт запустите, вы находитесь в новой оболочке, другой интерактивной оболочке 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. Вы можете обнаружить, что приглашение после команд выхода появляется в забавных местах.


обратите внимание на дискуссию в комментариях. Это не решение, которое я бы рекомендовал, но он достигает заявленной цели одного скрипта для установки среды, которая работает со всеми оболочками (которые принимают -i возможность сделать интерактивную оболочку). Вы также можете добавить "$@" после опции ретрансляции любых других аргументов, которые затем могут сделать оболочку полезной в качестве общего инструмента "установить среду и выполнить команду". Вы, возможно, захотите, чтобы пропустить -i если есть другие аргументы, ведущие к:

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

The "${@-'-i'}" бит означает 'если аргумент список содержит хотя бы один аргумент, используйте исходный список аргументов; в противном случае замените -i для несуществующих аргументов.

вы должны использовать модули, см. http://modules.sourceforge.net/

EDIT: пакет модулей не обновлялся с 2012 года, но по-прежнему работает нормально для основ. Все новые функции, колокола и свистки происходят в lmod в этот день (что мне нравится больше):https://www.tacc.utexas.edu/research-development/tacc-projects/lmod

еще один обходной путь, который я не вижу упоминания, - это запись значения переменной в файл.

я столкнулся с очень похожей проблемой, когда я хотел иметь возможность запустить последний тест набора (вместо всех моих тестов). Мой первый план состоял в том, чтобы написать одну команду для установки переменной env TESTCASE, а затем иметь другую команду, которая будет использовать это для запуска теста. Излишне говорить, что у меня была такая же проблема, как и у вас.

но потом я придумал этот простой Хак:

первая команда ( testset ):

#!/bin/bash

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

вторая команда (testrun ):

#!/bin/bash

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

добавьте флаг-l в верхней части вашего скрипта bash, т. е.

#!/usr/bin/env bash -l

...

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

значения NAME1 и NAME2 теперь будут экспортированы в текущую среду, однако эти изменения не являются постоянными. Если вы хотите, чтобы они были постоянными, вам нужно добавить их в свой .bashrc файл или другой init-файл.

из man-страницы:

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

вы можете поручить дочернему процессу распечатать свои переменные среды (вызвав "env"), затем выполнить цикл над печатными переменными среды в Родительском процессе и вызвать "экспорт" для этих переменных.

следующий код основан на захват вывода найти . - print0 в массив bash

если родительская оболочка является bash, вы можете использовать

while IFS= read -r -d $'' 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 $'' 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

вы можете вызвать еще один Bash с другим bash_profile. Кроме того, вы можете создать специальный bash_profile для использования в среде multi-bashprofile.

помните, что вы можете использовать функции внутри bashprofile, и что функции будут доступны в глобальном масштабе. например, "function user { export USER_NAME $1 }" может установить переменную во время выполнения, например: user olegchir & & env | grep olegchir

технически это правильно - только "eval" не разветвляет другую оболочку. Однако с точки зрения приложения, которое вы пытаетесь запустить в измененной среде, разница равна нулю: дочерний элемент наследует среду своего родителя, поэтому (измененная) среда передается всем нисходящим процессам.

Ipso facto, измененная переменная среды 'прилипает' -- до тех пор, пока вы работаете под родительской программой/оболочкой.

Если это абсолютно необходимо, чтобы переменная окружения оставалась после выхода родительского элемента (Perl или оболочки), необходимо, чтобы родительская оболочка выполняла тяжелую работу. Один из методов, который я видел в документации, заключается в том, что текущий скрипт создает исполняемый файл с необходимым языком "экспорта", а затем обманывает родительскую оболочку в его выполнении-всегда осознавая тот факт, что вам нужно предварить команду "source", если вы пытаетесь оставить энергонезависимую версию измененной версии. окружающая среда позади. В лучшем случае Клюг.

второй метод заключается в изменении скрипта, который инициирует среду оболочки (.bashrc или что-то еще), чтобы содержать измененный параметр. Это может быть опасно - если вы используете сценарий инициализации, он может сделать вашу оболочку недоступной при следующей попытке запуска. Существует множество инструментов для изменения текущей оболочки; прикрепляя необходимые настройки к "пусковой установке", вы эффективно продвигаете эти изменения вперед, как что ж. Как правило, это не очень хорошая идея; если вам нужны только изменения среды для конкретного пакета приложений, вам придется вернуться и вернуть сценарий запуска оболочки в его первоначальное состояние (используя vi или что-то еще) после этого.

короче говоря, нет хороших (и простых) методов. Предположительно это было сделано с трудом, чтобы обеспечить безопасность системы не была безвозвратно скомпрометирована.

короткий ответ-Нет, вы не можете изменить среду родительского процесса, но похоже, что вы хотите создать среду с пользовательскими переменными среды и оболочкой, которую выбрал пользователь.

Так почему бы просто не что-то вроде

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

затем, когда вы закончите с окружающей средой, просто exit.

вы всегда можете использовать псевдонимы

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

другой вариант-использовать "модули среды" (http://modules.sourceforge.net/). это, к сожалению, вводит третий язык в микс. Вы определяете среду с помощью языка Tcl, но есть несколько удобных команд для типичных модификаций (prepend vs.append vs set). Вам также потребуется установить модули среды. Затем вы можете использовать module load *XXX* чтобы назвать среду, которую вы хотите. Команда модуля-это в основном причудливый псевдоним для eval описанный выше механизм Томас Каммайер. Главным преимуществом здесь является то, что вы можете поддерживать среду на одном языке и полагаться на "модули среды", чтобы перевести ее на sh, ksh, bash, csh, tcsh, zsh, python (?!?!!), прием.

Я сделал это много лет назад. Если я правильно помню, я включил псевдоним в каждый из них .bashrc и и .cshrc, с параметрами, сглаживание соответствующих форм установки среды в общую форму.

тогда сценарий, который вы будете создавать в любой из двух оболочек, имеет команду с этой последней формой, которая подходит для псевдонимов в каждой оболочке.

Если я найду конкретные псевдонимы, я опубликую их.

Я создал решение, используя трубы, eval и сигнал.

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'

Он должен быть готов к использованию до тех пор, пока у вас есть папка, содержащая файл сценария, добавленный к пути.

Я не вижу никакого ответа, документирующего, как обойти эту проблему с взаимодействующими процессами. Общий шаблон с такими вещами, как 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;

(Как видите,csh и tcsh использует setenv установить varibles.)

ваша собственная программа тоже может это сделать.

bash$ foo=$(makefoo)

код makefoo скрипт просто вычислит и напечатает значение, и пусть вызывающий абонент делает с ним все, что хочет-назначение его переменной является обычным случаем использования, но, вероятно, не то, что вы хотите жестко закодировать в инструмент, который производит значение.

это не то, что я бы назвал выдающимся, но это также работает, если вам нужно вызвать скрипт из оболочки в любом случае. Это не очень хорошее решение, но для одной статической переменной среды оно работает достаточно хорошо.

1.) Создайте скрипт с условием, выходящим либо из 0 (успешно), либо из 1 (Не успешно)

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

2.) Создать псевдоним, который зависит от кода выхода.

alias='myscript.sh && export MyVariable'

вы вызываете псевдоним, который вызывает скрипт, который вычисляет условие, которое требуется для выхода из нуля через'&&', чтобы установить переменную окружения в родительской оболочке.

это обломки, но это может быть полезно в крайнем случае.

кроме условных обозначений записи в зависимости от того, какой $SHELL/$TERM установлен, нет. Что плохого в использовании Perl? Это довольно распространено (я не могу придумать ни одного варианта UNIX, у которого его нет), и это избавит вас от проблем.