как экспортировать переменные VAR из подшелла в родительский шелл?

Вопрос или проблема

У меня есть скрипт Korn shell

#!/bin/ksh
# установить правильное ENV
case $INPUT in
abc)
  export BIN=${ABC_BIN}
  ;;
  def)
  export BIN=${DEF_BIN}
  ;;
  *)
  export BIN=${BASE_BIN}
  ;;
esac
# exit 0 < - плохая идея для включения файла

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

Я знаю о

. .myscript.sh

но есть ли способ сделать это без 'включения'? Так как мои пользователи часто забывают 'включить'.


EDIT1: убираю часть "exit 0" - это было просто я, типище без раздумий

EDIT2: чтобы добавить больше деталей о том, почему это мне нужно: мои разработчики пишут код для (для простоты) 2 приложений: ABC и DEF. Каждое приложение запускается в производстве разными пользователями usrabc и usrdef, поэтому у них настроены $BIN, $CFG, $ORA_HOME и т.д., специфичные для их приложений.

Так что

  • $BIN ABC = /opt/abc/bin # $ABC_BIN в вышеупомянутом скрипте
  • $BIN DEF = /opt/def/bin # $DEF_BIN

и т.д.

Теперь, на dev box разработчики могут разрабатывать и ABC, и DEF одновременно под своей учетной записью 'justin_case', и я заставляю их включать файл (выше), чтобы они могли переключать настройки своих переменных окружения туда-сюда. ($BIN должен указывать на $ABC_BIN в один момент, а затем мне нужно переключиться на $BIN=$DEF_BIN)

Теперь скрипт должен также создавать новые песочницы для параллельной разработки одного и того же приложения и т.д. Это заставляет меня делать это интерактивно, запрашивая имя песочницы и т.д.

  • /home/justin_case/sandbox_abc_beta2
  • /home/justin_case/sandbox_abc_r1
  • /home/justin_case/sandbox_def_r1

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

  • alias 'setup_env=. .myscript.sh'

и запускать его с

  • setup_env parameter1 ... parameterX

Сейчас это имеет больше смысла для меня

Я думаю, это проблема "нельзя сделать"...

Во-первых, вы не хотите включать этот скрипт из-за exit 0 в конце.

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

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

Позвольте мне уточнить концепцию "обертки".

Допустим, вы хотите запустить программу snoopy с PROD или DEV в переменной окружения "OPTIONS" в зависимости от того, хотите ли вы производственную или развивающую среду. ЕСЛИ она не установлена, скажем, snoopy делает что-то странное, например, стирает базу данных для производства и разработки...

переименуйте "snoopy" в snoopy.bin (или .snoopy.bin)

затем поместите скрипт в том же месте с именем "snoopy", который содержит следующее:

#!/bin/sh

export OPTIONS

case "$OPTIONS" 
in
  PROD) ;;
  DEV) ;;
  *) OPTIONS=DEV ;;
esac

#файл бинарного исполнения на самом деле называется snoopy.bin 

exec "$0.bin" "$@"

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

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

Вы можете включить скрипт, используя ".", т.е.

. ./myscript.ksh

Может быть, вы попробуете...

#!/bin/bash

mknod fifo p

(
       echo 'value' > fifo &
)

VARIABLE=`cat fifo`
rm fifo

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

Хорошо, не смейтесь сейчас. Очень быстрое и очень грязное решение - добавить

echo "Пожалуйста, скопируйте и вставьте эту команду:"
echo ""
echo "export BIN=\"$BIN\""

в ваш скрипт.

Другой подход. Просто exec подчиненный $SHELL в вашем скрипте, может быть, с другим приглашением (изменение $PS1, чтобы сообщить пользователям, в какой среде они работают, чтобы уменьшить путаницу).

Другой подход (мой любимый). Пользователи забывают включить ваш скрипт, так почему это? Включение - это, собственно, стандартный способ. Может быть, хорошим способом напомнить им будет убрать первую строку (#!/bin/sh) и затем chmod a-x. Его все еще можно включить после этого, но его явно нельзя ошибочно выполнить.

Резюме: В целом, мне кажется, что у вас была странная идея с самого начала. Может быть, не странная... Я бы сказал, в какой-то степени не-Unix в стиле. Я никогда не нуждался в экспорте окружения к родителю в своей жизни. Я однажды видел аналогичную ситуацию - вход .profile, запрашивающий, какую из трех различных сред установить для одной учетной записи oracle. Плохая идея, в конце концов оказалось, что пользователи предпочли мигрировать на sudo (они sudo к трем разным аккаунтам oraxxx). Что вы пытаетесь достичь, если можно спросить?

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

Но, так как 'сын' процесс может "~отправлять команды~" своему 'отцу' процессу, будьте осторожны и используйте это на свой страх и риск.

Вот пример, где отец процесс учится от сына процесса, а сын процесс учится от своего отца процесса (каждый процесс может передавать значения друг другу).

Этот пример ничего другого интересного не делает в данный момент, кроме обмена значениями и использования их в качестве переменных между 2 процессами (отец <--> сын), но вы можете вызывать сторонние функции в главном цикле (процесс отца) или в цикле сына (процесс сына) или в обоих.

Рассмотрите следующий скрипт:

#!/bin/bash
# Тестирование nba bash fd & каналы для двунаправленной связи между 2 процессами

# Очистка fifo, если существует
[[ -p /dev/shm/tfpfifo ]] && rm -f /dev/shm/tfpfifo
[[ -p /dev/shm/rfpfifo ]] && rm -f /dev/shm/rfpfifo

# Создание fifo, если не существует
[[ ! -p /dev/shm/tfpfifo ]] && mkfifo /dev/shm/tfpfifo
[[ ! -p /dev/shm/rfpfifo ]] && mkfifo /dev/shm/rfpfifo

# выход из fifo, если fifo не существует
[[ ! -p /dev/shm/tfpfifo ]] && echo "ERROR: FIFO NOT CREATED" && exit 29
[[ ! -p /dev/shm/rfpfifo ]] && echo "ERROR: FIFO NOT CREATED" && exit 29

# Определение (и инициализация чем-то) переменных файловых дескрипторов (будут перезаписаны bash!)
fdt=fdtfp;
fdr=fdrfp;

# Соединение файловых дескрипторов с каналами fifo (один отец -> сын и один сын -> отец)
exec {fdt}<>/dev/shm/tfpfifo; 
exec {fdr}<>/dev/shm/rfpfifo; 

# Определение возврата каретки
CR=$(echo -en "\\r")

# Определение поворота цикла 
k=0

# Основной циклов
while read <&${fdt}
do
    rep=${REPLY//$CR/}
    cur_pid=$BASHPID
    cur_ppid=$PPID
    [[ ${rep} == 'start' ]] \
        && echo -e "\rfrom_pipe:${rep}" || echo -e "\nmy_catched_reply_from_son:${rep}"
    REP=$(echo -e "${rep}" |tr " " "\n")
    my_son_pid=$(echo -e "${REP}"| grep son_pid: |cut -d: -f2)
    my_cur_pid=$(echo -e "${REP}"| grep son_ppid: |cut -d: -f2)
    my_cur_ppid=$(echo -e "${REP}"| grep son_pppid: |cut -d: -f2)
    [[ ${my_son_pid} == "" ]] && while read <&${fdr} 
    do 
        reply=${REPLY//$CR/}
        local_pid=$BASHPID
        SON_REP=$(echo -e "${reply}" |tr " " "\n")
        parent_pid=$(echo -e "${SON_REP}"| grep cur_pid: |cut -d: -f2)
        pparent_pid=$(echo -e "${SON_REP}"| grep cur_ppid: |cut -d: -f2)
        k=$(echo -e "${SON_REP}"| grep k: |cut -d: -f2)

        echo -e "\nson_catched_reply_from_me:${reply}"
        [[ ${k} != "" && ${parent_pid} != "" && ${pparent_pid} != "" ]] \
            && [[ $k -ne 3 ]] \
            && echo son_pid:$local_pid son_ppid:${parent_pid} son_pppid:${pparent_pid} >&${fdt}
        echo -e "son_k=$k son_BASHPID=$BASHPID"
        sleep .1
    done &

    [[ $k -ne 4 ]] \
    && echo -e "k:${k} cur_pid:${cur_pid} cur_ppid:${cur_ppid}" >&${fdr}

    [[ ${my_son_pid} != "" ]] && echo -e "my_son_pid_recieved_from_son=$my_son_pid"
    [[ ${my_cur_pid} != "" ]] && echo -e "my_cur_pid_recieved_from_son=$my_cur_pid"
    [[ ${my_cur_ppid} != "" ]] && echo -e "my_cur_ppid_recieved_from_son=$my_cur_ppid"
    [[ $k -eq 0 ]] && echo -e "\rk=$k my_BASHPID=$BASHPID - первый раунд" \
        || echo -e "k=$k my_BASHPID=$BASHPID"
    sleep 1
    ((k++))
    [[ $k -eq 4 ]] && kill $my_son_pid && kill $my_cur_pid
done &

# Начало работы
ps -ef | grep -q "$!" \
    && ([[ "$my_son_pid" == "" ]] && echo start >&${fdt}) \
    || (exec {fdt}>&- && exec {fdr}>&-)

# Удаление файловых дескрипторов после работы 
sleep 5 && exec {fdt}>&- && exec {fdr}>&-

# Очистка fifo, если существует
[[ -p /dev/shm/tfpfifo ]] && rm -f /dev/shm/tfpfifo
[[ -p /dev/shm/rfpfifo ]] && rm -f /dev/shm/rfpfifo

Вывод (я оставляю 3 раунда в цикле, чтобы показать, что связь может продолжаться, пока каждый процесс может "кормить" друг друга, читая fifo, и чтобы показать, что вы можете контролировать количество раундов с помощью хорошо определенного 'k' или подобного метода):

from_pipe:start
k=0 my_BASHPID=1158894 - первый раунд

son_catched_reply_from_me:k:0 cur_pid:1158894 cur_ppid:4163
son_k=0 son_BASHPID=1159116

my_catched_reply_from_son:son_pid:1159116 son_ppid:1158894 son_pppid:4163
my_son_pid_recieved_from_son=1159116
my_cur_pid_recieved_from_son=1158894
my_cur_ppid_recieved_from_son=4163
k=1 my_BASHPID=1158894

son_catched_reply_from_me:k:1 cur_pid:1158894 cur_ppid:4163
son_k=1 son_BASHPID=1159116

my_catched_reply_from_son:son_pid:1159116 son_ppid:1158894 son_pppid:4163
my_son_pid_recieved_from_son=1159116
my_cur_pid_recieved_from_son=1158894
my_cur_ppid_recieved_from_son=4163
k=2 my_BASHPID=1158894

son_catched_reply_from_me:k:2 cur_pid:1158894 cur_ppid:4163
son_k=2 son_BASHPID=1159116

my_catched_reply_from_son:son_pid:1159116 son_ppid:1158894 son_pppid:4163
my_son_pid_recieved_from_son=1159116
my_cur_pid_recieved_from_son=1158894
my_cur_ppid_recieved_from_son=4163
k=3 my_BASHPID=1158894

son_catched_reply_from_me:k:3 cur_pid:1158894 cur_ppid:4163
son_k=3 son_BASHPID=1159116

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

Ответ или решение

Экспорт переменных окружения (VARs) из дочернего процесса (субшелла) в родительский процесс (родительский шелл) в Unix-подобных системах, таких как Korn shell (ksh), представляет собой интересную и сложную задачу, поскольку по умолчанию дочерние процессы не имеют возможности изменять окружение родительского процесса. Рассмотрим несколько способов решения этой проблемы, с фокусом на ваш сценарий.

1. Понимание механизма экспорта переменных

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

2. Использование Sourcing (необходимость)

Наиболее стандартным методом является использование команды source или . (точка) для выполнения скрипта в контексте текущего процесса. Например, вы можете выполнить ваш скрипт следующим образом:

. ./myscript.sh

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

3. Обходные пути без использования Sourcing

Для тех случаев, когда sourcing нежелателен, существуют другие подходы:

А. Вывод команды для копирования

Вы можете перечислить все необходимые экспорты в вашем скрипте, чтобы пользователи могли легко их скопировать и вставить. Например:

echo "Пожалуйста, выполните следующую команду:"
echo "export BIN=\"$BIN\""

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

B. Использование обертки (wrapper)

Вы можете создать обертку для команд, которые требуют настройки окружения. Например, создайте скрипт, который будет настраивать окружение и затем вызывать нужную команду:

#!/bin/bash

case "$1" in
  abc)
    export BIN=${ABC_BIN}
    ;;
  def)
    export BIN=${DEF_BIN}
    ;;
  *)
    export BIN=${BASE_BIN}
    ;;
esac

exec "$@"

Этот скрипт будет выставлять переменные окружения и запускать команду, переданную как аргумент. Это позволяет избежать необходимости manual sourcing и гарантирует правильное выполнение окружения.

C. Использование FIFO для взаимодействия между процессами

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

4. Идеи по улучшению

Как дополнительная мера можно создать алиасы в профилях пользователей:

alias setup_env='source ./myscript.sh'

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

Заключение

Проблема передачи значений переменных окружения из дочернего процесса в родительский относится к особенностям работы Unix-подобных систем. Наиболее оптимальным и распространенным способом является использование source. Если этого избежать не удается, различные обходные решения, такие как вывод необходимых команд, обертки и создание алиасов, могут значительно упростить эту задачу. Исполнение этих стратегий в вашем сценарии может помочь вашим пользователям легче управлять своими окружениями.

Оцените материал
Добавить комментарий

Капча загружается...