Установить переменную родительской оболочки из подоболочки

Как установить переменную в родительской оболочке из подоболочки?

a=3
(a=4)
echo $a

11 ответов

Решение

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

Вам нужна подоболочка? Если вам просто нужна группа, используйте фигурные скобки:

a=3
{ a=4;}
echo $a

дает 4 (будьте осторожны с пробелами в этом). В качестве альтернативы, запишите значение переменной в stdout и запишите его в вызывающую программу:

a=3
a=$(a=4;echo $a)
echo $a

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

Есть хак с переменной gdb-bash:

gdb --batch-silent -ex "attach $$" -ex 'set bind_variable("a", "4", 0)'; 

хотя это всегда устанавливает переменную в глобальной области видимости, а не только в родительской области видимости

Вы не У подоболочки нет доступа к среде его родителя. (По крайней мере, внутри абстракции, которую предоставляет Bash. Вы можете попытаться использовать gdbили разбить стек, или еще много чего, чтобы получить такой доступ тайно. Я не рекомендовал бы это, все же.)

Одной из альтернатив является то, что подоболочка может записывать операторы присваивания во временный файл, чтобы его родитель читал

a=3
(echo 'a=4' > tmp)
. tmp
rm tmp
echo "$a"

Если проблема связана с циклом while, один из способов исправить это - использовать Process Substitution:

    var=0
    while read i;
    do
      # perform computations on $i
      ((var++))
    done < <(find . -type f -name "*.bin" -maxdepth 1)

как показано здесь: /questions/12549391/dilemma-poka-petlya-v-bash/12549408#12549408

Прочитав ответ от user978917 (спасибо) с подходом к временным файлам и комментариями с просьбой о решении файловых дескрипторов, у меня возникла следующая идея:

      a=3    
. <(echo a=4; echo b=5)
echo $a
echo $b
  • Это позволяет одновременно возвращать разные переменные (что может быть проблемой в варианте подоболочки принятого ответа).
  • Итерация не нужна,
  • Нет временного файла, о котором нужно заботиться.
  • Близко к синтаксису, предложенному ОП.

Результат:

      4
5

При включенном xtrace видно, что мы получаем данные из файлового дескриптора, созданного для вывода подоболочки:

      + a=3
+ . /dev/fd/63 # <-- the file descriptor ;)
++ echo a=4
++ echo b=5
++ a=4
++ b=5
+ echo 4
4
+ echo 5
5

Чтобы изменить переменные в скрипте, вызываемом из родительского скрипта, вы можете вызвать скрипт, которому предшествует "."

a=3
echo $a    
. ./calledScript.sh
echo $a

в namedScript.sh

a=4

Ожидаемый результат

3
4

Вместо доступа к переменной из родительской оболочки измените порядок команд и используйте подстановку процесса :

      a=3
echo 5 | (read a)
echo $a

печатает 3

      a=3
read a < <(echo 5)
echo $a

отпечатки 5

Другой пример:

      let i=0 
seq $RANDOM | while read r
              do 
                let i=r 
              done
echo $i

против

      let i=0
while read r 
do 
  let i=r 
done < <(seq $RANDOM)
echo $i

Вы можете вывести значение в подоболочке и назначить вывод подоболочки переменной в скрипте вызывающей стороны:

# subshell.sh
echo Value

# caller
myvar=$(subshell.sh)

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

# subshell.sh
echo "Writing value" 1>&2
echo Value

# caller
myvar=$(subshell.sh 2>/dev/null) # or to somewhere else
echo $myvar

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

# subshell.sh
echo "a=4"

# caller
# export $(subshell.sh) would be more secure, since export accepts name=value only.
eval $(subshell.sh)
echo $a

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

Мне нравится концепция позднего связывания. Постоянный запуск оболочки Bash с помощью наиболее полезного $(подстановка команд) - почти достаточная причина для перехода на другой язык.

Файлы, однако, являются глобальными переменными bash...

Создайте функцию set/get/default следующим образом:

globalVariable() { # NEW-VALUE
    # set/get/default globalVariable
    if [ 0 = "$#" ]; then
        # new value not given -- echo the value
        [ -e "$aRam/globalVariable" ] \
            && cat "$aRam/globalVariable" \
            || printf "default-value-here"
    else
        # new value given -- set the value
        printf "%s" "$1" > "$aRam/globalVariable"
    fi
}

"$ aRam" - это каталог, в котором хранятся значения. Мне нравится, что это диск памяти для скорости и волатильности:

aRam="$(mktemp -td $(basename "$0").XXX)" # temporary directory
mount -t tmpfs ramdisk "$aRam" # mount the ram disk there
trap "umount "$aRam" && rm -rf "$aRam"" EXIT # auto-eject

Чтобы прочитать значение:

v="$(globalVariable)" # or part of any command

Чтобы установить значение:

globalVariable newValue # newValue will be written to file

Чтобы сбросить значение:

rm -f "$aRam/globalVariable"

Единственная реальная причина для функции доступа - применить значение по умолчанию, потому что cat выдаст ошибку из-за несуществующего файла. Также полезно применять другую логику get / set. В противном случае, это не будет нужно вообще.

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

v="$(cat "$aRam/globalVariable 2>/dev/null")"

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

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

Вы можете передать имя переменной в функцию, которая использует local -nчтобы превратить его в специальную переменную, называемую nameref:

      myfunc() {
    local -n OUT=$1
    local -n SIDEEFFECT=$2
    OUT='foo'
    SIDEEFFECT='bar'
}

myfunc A B
echo $A
> foo
echo $B
> bar

Это метод, который я в конечном итоге использовал вместо получения подоболочки FOO=$(myfunc) рабочая установка нескольких переменных.

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

      function ComplexReturn(){
  # do your processing... 
  a=123
  b=456
  echo -n "AAA=${a}; BBB=${b};"
}
# ... this can be internal function or any subshell command
eval $(ComplexReturn)
echo $AAA $BBB
Другие вопросы по тегам