Установить переменную родительской оболочки из подоболочки
Как установить переменную в родительской оболочке из подоболочки?
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