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

317

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

setenv FOO foo

в csh/tcsh или

export FOO=foo

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

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

source myscript

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

Но здесь rub:

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

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

  • 0
    @eusoubrasileiro, который не работает (по крайней мере, на OSX), так как «экспорт» интерпретируется bash как имя файла.
  • 0
    см. ответ @Humberto Romero stackoverflow.com/a/28489593/881375 в этой теме
Показать ещё 2 комментария
Теги:
tcsh
csh

21 ответ

225
Лучший ответ

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

  • 4
    Этот ответ не является правильным или, по крайней мере, вводит в заблуждение - это можно сделать с помощью нотации сценария с точечным пространством, описанной в ответах @Humberto.
  • 6
    Спасибо за исправление. Я соответственно смущен. Я искал быстрый ответ, чтобы заставить env vars оставаться в моей оболочке - не то же самое, что OP, на который вы ответили очень хорошо.
212

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

. /path/to/set_env_vars.sh

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

. set_env_vars.sh

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

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

  • 17
    Другими словами, пространство точек заменяет source bash в других оболочках.
  • 2
    Я заметил, что это не будет работать, если вы передадите вывод, например, "./script.sh | tee out.log"
Показать ещё 10 комментариев
52

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

Одна вещь, которую вы можете сделать, это написать script, который испускает правильные команды для tcsh или sh основывается на том, как он вызывается. Если вы script является "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`'

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

Это в значительной степени связано с тем, как выполнялись параметры терминала для входа в систему: script выводит статуты, которые будут выполняться в оболочке входа. Псевдоним, как правило, использовался бы, чтобы сделать обращение простым, как в "tset vt100". Как уже упоминалось в другом ответе, на сервере новостей INN UseNet есть аналогичные функции.

  • 0
    Я думаю, что это может быть на правильном пути. Но я не совсем знаю, что должно быть в «setit», чтобы он мог корректно работать из любой оболочки. Можете ли вы объяснить немного больше, что вы имели в виду?
  • 1
    По сути, он будет проверять $ 0 и переходить в соответствующую часть скрипта в зависимости от того, с каким именем он называется.
Показать ещё 5 комментариев
42

В моем .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
}

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

  • 0
    Это именно то, что мне нужно (ну, мне пришлось изменить номер порта;).
23

Это "возможно" с помощью 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
  • 0
    Kjetil, чувак, это фантастика. Я действительно наслаждаюсь вашим сценарием прямо сейчас.
  • 0
    Это круто! Но как это сделать в Mac?
Показать ещё 2 комментария
12

Это работает — это не то, что я бы использовал, но он "работает". Позвольте создать script teredo, чтобы установить переменную среды TEREDO_WORMS:

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

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

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

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

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


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

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

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

  • 1
    Хм, это довольно радикально: вы заменяете оболочку входа в систему. Если вы собираетесь сделать это ... вы должны проверить, как это влияет на сеанс и группу процессов, а также на другие вещи. Например: как вы думаете, что происходит с управляемыми дочерними процессами?
  • 2
    Несомненно - поэтому я сказал, что не буду его использовать. Если вы выполняете дважды, вы не теряете информацию о сеансе или группе процессов; это основано на PID, и PID не изменяется. В профиле или в файле входа в систему он проходит через скрипт настройки общей языковой среды. Но, как я уже сказал, я бы не стал этим пользоваться.
Показать ещё 4 комментария
11

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

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

  • 1
    Здесь мы широко используем файлы модулей, и одной из причин является поддержка csh / bourne-ish. У нас есть устаревшие сценарии csh, сценарии bash и сценарии python, и все они получают настройки переменных среды из одних и тех же файлов модулей, вместо того чтобы иметь набор сценариев env.csh, env.sh, env.py с дополнительным обслуживанием, которое влечет за собой. Кроме того, модульные файлы позволяют вашей среде отражать зависимости от версии: если вам нужно перейти на версию 3 с версии 4 инструмента, вместо того, чтобы вручную сбросить все ваши env-переменные, вы можете просто поменять модуль и все изменится.
  • 0
    Я не мог найти примеры того, как его использовать, каждая попытка была неудачной, какие-нибудь советы?
Показать ещё 3 комментария
7

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

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

Но потом я придумал этот простой взлом:

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

#!/bin/bash

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

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

#!/bin/bash

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

Добавьте флаг -l в начало bash script i.e.

#!/usr/bin/env bash -l

...

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

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

Из справочных страниц:

-l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
  • 0
    Нет, на самом деле не работает. Все, что происходит, это то, что ваш скрипт думает, что он работает в оболочке входа. По-прежнему не предоставляет переменные вызывающей оболочке.
3

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

Следующий код основан на Захват вывода 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
2

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

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

  • 1
    Ничто из этого не повлияет на вызывающую оболочку.
  • 0
    @Ignacio, в этом случае вам не нужно вызывать скрипты для установки переменных среды. «Вызывающая» оболочка сама установит переменную. Но если нам все еще нужно отделить сеттеры от основного кода bashrc, мы можем разделить все эти функции на отдельный файл и включить его в виде библиотеки (например, «source ru.olegchir.myproject.environment.setters.sh» в. Bashrc).
1

Я создал решение, используя трубы, 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

Он может работать с любой командой.

1

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

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

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

1

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

1

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

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

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

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

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

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

1

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

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

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

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

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

0

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

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

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

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

alias='myscript.sh && export MyVariable'

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

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

0

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

0

В OS X bash вы можете сделать следующее:

Создайте файл bash script, чтобы отключить переменную

#!/bin/bash
unset http_proxy

Сделать исполняемый файл

sudo chmod 744 unsetvar

Создать псевдоним

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

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

  • 1
    По какой причине вы просто не используете alias unsetvar='unset http_proxy' ? Или еще лучше создайте функцию unsetvar () { unset http_proxy; }
  • 0
    Это не только для OS X. Это может работать и для Linux. Этот ответ также будет лучше, если вы напишите, в каких файлах вы работаете.
-10

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

  • 0
    Как Perl решает проблему? Программа Perl все еще не может установить переменные окружения вызывающей оболочки, не так ли?
  • 0
    Нет. Однако он может установить его с помощью Local :: Env, а затем вызвать скрипт оболочки с помощью system () или обратных галочек.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню