Я работаю с этим:
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
У меня есть сценарий, как показано ниже:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
test1
echo "$e"
Что возвращает:
hello
4
Но если я присвою результат функции переменной, глобальная переменная e
не изменится:
#!/bin/bash
e=2
function test1() {
e=4
echo "hello"
}
ret=$(test1)
echo "$ret"
echo "$e"
Возврат:
hello
2
Я слышал об использовании eval в этом случае, поэтому сделал это в test1
:
eval 'e=4'
Но результат тот же.
Не могли бы вы мне объяснить, почему он не модифицирован? Как я могу сохранить эхо test1
функции ret
и изменить глобальную переменную?
bash
variables
global-variables
eval
harrison4
источник
источник
Ответы:
Когда вы используете подстановку команд (т. Е.
$(...)
Конструкцию), вы создаете подоболочку. Подоболочки наследуют переменные от своих родительских оболочек, но это работает только одним способом - подоболочка не может изменять среду своей родительской оболочки. Ваша переменнаяe
устанавливается в подоболочке, но не в родительской оболочке. Есть два способа передать значения из подоболочки в ее родительскую. Сначала вы можете вывести что-то в стандартный вывод, а затем записать это с помощью подстановки команды:Дает:
Для числового значения от 0 до 255 вы можете использовать
return
для передачи числа в качестве статуса выхода:Дает:
источник
setarray() { declare -ag "$1=(a b c)"; }
Резюме
Ваш пример можно изменить следующим образом, чтобы заархивировать желаемый эффект:
печатает по желанию:
Обратите внимание, что это решение:
e=1000
тоже.$?
если нужно$?
Единственные отрицательные побочные эффекты:
bash
._
)_capture
просто заменить все места где3
с другой (выше) числом.Следующее (довольно длинное, извините за это), надеюсь, объясняет, как применить этот рецепт и к другим скриптам.
Эта проблема
выходы
в то время как желаемый результат
Причина проблемы
Переменные оболочки (или, вообще говоря, среда) передаются от родительских процессов дочерним процессам, но не наоборот.
Если вы выполняете захват вывода, это обычно выполняется в подоболочке, поэтому обратная передача переменных затруднена.
Некоторые даже говорят, что это невозможно исправить. Это неправильно, но это давно известная проблема, которую трудно решить.
Есть несколько способов решить эту проблему лучше всего, это зависит от ваших потребностей.
Вот пошаговое руководство, как это сделать.
Передача переменных в родительскую оболочку
Есть способ передать переменные родительской оболочке. Однако это опасный путь, потому что он использует
eval
. Если все будет сделано неправильно, вы рискуете многим злом. Но если все сделано правильно, это совершенно безопасно, при условии, что в нем нет ошибкиbash
.печатает
Обратите внимание, что это работает и для опасных вещей:
печатает
Это связано с тем
printf '%q'
, что все цитируется таким образом, что вы можете безопасно повторно использовать его в контексте оболочки.Но это головная боль ..
Это не только выглядит некрасиво, но и требует большого объема ввода, поэтому подвержено ошибкам. Всего одна ошибка, и вы обречены, верно?
Что ж, мы на уровне оболочки, так что вы можете его улучшить. Просто подумайте об интерфейсе, который вы хотите видеть, а затем вы сможете его реализовать.
Дополните, как оболочка обрабатывает вещи
Давайте сделаем шаг назад и подумаем о каком-нибудь API, который позволяет нам легко выразить то, что мы хотим сделать.
Что же нам делать с
d()
функцией?Мы хотим записать результат в переменную. Хорошо, тогда давайте реализуем API именно для этого:
Теперь вместо того, чтобы писать
мы можем написать
Что ж, похоже, мы не сильно изменились, поскольку, опять же, переменные не передаются обратно
d
в родительскую оболочку, и нам нужно ввести еще немного.Однако теперь мы можем использовать всю мощь оболочки, поскольку она красиво обернута функцией.
Подумайте о простом для повторного использования интерфейсе
Во-вторых, мы хотим быть СУХИМИ (не повторяйся). Поэтому мы категорически не хотим вводить что-то вроде
x
Здесь не только излишним, это чревато ошибками всегда repeate в правильном контексте. Что, если вы используете его 1000 раз в скрипте, а затем добавите переменную? Вы категорически не хотите изменять все 1000 мест, в которыхd
.Так что оставьте это
x
прочь, чтобы мы могли написать:выходы
Это уже выглядит очень хорошо. (Но все еще есть тот,
local -n
который не работает в обычныхbash
3.x)Избегайте изменений
d()
У последнего решения есть несколько серьезных недостатков:
d()
нужно изменитьxcapture
Для передачи вывода необходимо использовать некоторые внутренние детали .output
, поэтому мы никогда не сможем передать ее обратно._passback
Можем ли мы избавиться и от этого?
Конечно, мы можем! Мы находимся в оболочке, поэтому есть все, что нам нужно для этого.
Если вы присмотритесь к звонку поближе,
eval
то увидите, что у нас 100% контроль над этим местом. «Внутри»eval
мы находимся в подоболочке, поэтому мы можем делать все, что захотим, не опасаясь причинить вред родительской оболочке.Да, хорошо, давайте добавим еще одну оболочку, теперь прямо внутри
eval
:печатает
Однако и здесь есть серьезный недостаток:
!DO NOT USE!
фломастеры есть, потому что есть очень плохое состояние гонки в этом, что вы не можете легко видеть:>(printf ..)
фоновая работа. Таким образом, он все еще может выполняться во время_passback x
работы.sleep 1;
доprintf
или_passback
._xcapture a d; echo
затем выдаетx
илиa
сначала соответственно._passback x
должно быть частью_xcapture
, потому что это затрудняет повторное использование этого рецепта.$(cat)
), но, поскольку это решение,!DO NOT USE!
я выбрал кратчайший путь.Однако это показывает, что мы можем сделать это без изменений
d()
(и безlocal -n
)!Обратите внимание, что нам это не обязательно
_xcapture
, так как мы могли бы написать все прямо вeval
.Однако делать это обычно не очень удобно. И если вы вернетесь к своему сценарию через несколько лет, вы, вероятно, захотите без особых проблем прочитать его снова.
Исправить гонку
Теперь исправим состояние гонки.
Уловка может заключаться в том, чтобы дождаться, пока
printf
не закроется его STDOUT, а затем вывестиx
.Есть много способов заархивировать это:
Следующий путь может выглядеть так (обратите внимание, что он делает
printf
последний, потому что здесь это работает лучше):выходы
Почему это правильно?
_passback x
напрямую разговаривает со STDOUT.>&3
.$("${@:2}" 3<&-; _passback x >&3)
после_passback
, когда подоболочка закрывает STDOUT.printf
не может произойти раньше_passback
, независимо от того, сколько времени_passback
займет.printf
команда не выполняется до тех пор, пока не будет собрана полная командная строка, поэтому мы не можем увидеть артефактыprintf
, независимо от того , какprintf
она реализована.Следовательно, сначала
_passback
выполняется, а затемprintf
.Это разрешает гонку, принося в жертву один фиксированный дескриптор файла 3. Вы, конечно, можете выбрать другой дескриптор файла в случае, если FD3 не является свободным в вашем сценарии оболочки.
Также обратите внимание на то,
3<&-
что защищает FD3 от передачи функции.Сделайте его более общим
_capture
содержит части, которые принадлежатd()
, что плохо с точки зрения возможности повторного использования. Как это решить?Что ж, сделайте это отчаянным способом, введя еще одну вещь, дополнительную функцию, которая должна возвращать правильные вещи, которая названа в честь исходной функции с
_
прикрепленным.Эта функция вызывается после реальной функции и может дополнять вещи. Таким образом, это можно прочитать как некоторую аннотацию, поэтому она очень удобочитаема:
все еще печатает
Разрешить доступ к коду возврата
Отсутствует только бит:
v=$(fn)
устанавливает$?
то, чтоfn
вернулось. Так что вы, вероятно, тоже этого захотите. Тем не менее, это требует более серьезной настройки:печатает
Есть еще много возможностей для улучшения
_passback()
может быть устранен с помощьюpassback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
_capture()
можно устранить с помощьюcapture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }
Решение загрязняет файловый дескриптор (здесь 3), используя его для внутренних целей. Вы должны иметь это в виду, если вам случится сдать FD.
Обратите внимание, что
bash
4.1 и выше{fd}
должны использовать некоторые неиспользуемые FD.(Возможно, я добавлю здесь решение, когда приду к вам.)
Обратите внимание, что именно поэтому я использую для помещения его в отдельные функции, например
_capture
, потому что размещение всего этого в одной строке возможно, но все труднее читать и пониматьВозможно, вы хотите также захватить STDERR вызываемой функции. Или вы хотите даже передавать и передавать более одного дескриптора файла из переменных и в них.
У меня пока нет решения, но вот способ поймать более одного FD , так что мы, вероятно, тоже можем передать обратно переменные.
Также не забывайте:
Это должно вызывать функцию оболочки, а не внешнюю команду.
Последние слова
Это не единственно возможное решение. Это один из примеров решения.
Как всегда, у вас есть много способов выразить вещи в оболочке. Так что не стесняйтесь улучшать и находить что-то лучше.
Представленное здесь решение далеко от совершенства:
bash
, поэтому, вероятно, его сложно перенести на другие оболочки.Однако я думаю, что это довольно просто использовать:
источник
Может быть, вы можете использовать файл, писать в файл внутри функции, а затем читать из файла. Я перешел
e
на массив. В этом примере пробелы используются как разделители при обратном чтении массива.Вывод:
источник
Что вы делаете, вы выполняете test1
$(test1)
во вспомогательной оболочке (дочерней оболочке), а дочерние оболочки не могут ничего изменить в родительской оболочке .
Вы можете найти его в руководстве по bash
Пожалуйста, проверьте: Вещи приведены в подоболочке здесь
источник
У меня была аналогичная проблема, когда я хотел автоматически удалить созданные мной временные файлы. Решение, которое я придумал, заключалось в том, чтобы не использовать подстановку команд, а скорее передать в функцию имя переменной, которая должна принимать окончательный результат. Например
Итак, в вашем случае это будет:
Работает и не имеет ограничений на «возвращаемое значение».
источник
Это связано с тем, что подстановка команд выполняется в подоболочке, поэтому, хотя подоболочка наследует переменные, изменения в них теряются при завершении подоболочки.
Ссылка :
источник
Решением этой проблемы без введения сложных функций и значительного изменения исходной является сохранение значения во временном файле и его чтение / запись при необходимости.
Этот подход очень помог мне, когда мне пришлось имитировать функцию bash, вызываемую несколько раз в тестовом примере летучих мышей.
Например, у вас может быть:
Недостатком является то, что вам может потребоваться несколько временных файлов для разных переменных. А также может потребоваться ввести
sync
команду для сохранения содержимого на диске между одной операцией записи и чтения.источник
Вы всегда можете использовать псевдоним:
источник