it-swarm.com.ru

Передача аргументов по ссылке

Я хочу спросить, можно ли передать аргументы функции скрипта по ссылке:

то есть сделать что-то похожее на C:

Void boo (int & myint) { myint= 5; }

main (){
    int t= 4;
    printf t; // t->4
    boo (t);
    printf t; // t ->5
}

Итак, в BASH я хочу сделать что-то вроде:

Function boo () 
{

    var1=$1       # now var1 is global to the scrip but using it outside
                  # this function makes me loose encapsulation

    local var2=$1 # so i should use a local variable ... but how to pass it back ?

    var2='new'    # only changes the local copy 
    #$1='new'     this is wrong of course ...
    # ${!1}='new' # can i somehow use indirect reference?
}           

# call boo
SOME_VAR='old'
echo $SOME_VAR # -> old
boo "$SOME_VAR"
echo $SOME_VAR # -> new

Любые мысли будут оценены.

34
Roman M

Это 2018 год, и этот вопрос заслуживает обновления. По крайней мере, в Bash, начиная с Bash 4.3-alpha, вы можете использовать namerefs для передачи аргументов функции по ссылке:

function boo() 
{
    local -n ref=$1
    ref='new' 
}

SOME_VAR='old'
echo $SOME_VAR # -> old
boo SOME_VAR
echo $SOME_VAR # -> new

Критические части здесь:

  • Передача имени переменной в boo, а не ее значения: boo SOME_VAR, а не boo $SOME_VAR.

  • Внутри функции, используя local -n=$1 для объявления nameref к переменной, названной $1, то есть это не ссылка на сам $1, а скорее на переменную имя которой$1 держит, т.е. SOME_VAR в нашем случае. Значение в правой части должно быть просто строкой с именем существующей переменной: не имеет значения, как вы получаете строку, поэтому такие вещи, как local -n ref="my_var" или local -n ref=$(get_var_name), тоже будут работать. declare также может заменить local в контекстах, которые позволяют/требуют этого. Смотрите главу о параметрах оболочки в Справочном руководстве Bash для получения дополнительной информации.

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

12
Sergey Shevchenko

С man-страницы Bash (Расширение параметров):

 Если первый символ параметра является восклицательным знаком (!), A 
 уровень переменной косвенности вводится. Bash использует значение 
 переменная, сформированная из остальной части параметра в качестве имени 
 переменная; эта переменная затем раскрывается, и это значение используется в 
 остальная часть замещения, а не значение параметра 
 сам. Это известно как косвенное расширение .

Поэтому ссылка - это имя переменной. Вот функция swap, использующая косвенную переменную , Для которой не требуется временная переменная:

function swap()
{   # 
    # @param VARNAME1 VARNAME2
    #
    eval "$1=${!2} $2=${!1}"
}

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
22
Andreas Spindler

Используйте вспомогательную функцию upvar:

# Assign variable one scope above the caller.
# Usage: local "$1" && upvar $1 value [value ...]
# Param: $1  Variable name to assign value to
# Param: $*  Value(s) to assign.  If multiple values, an array is
#            assigned, otherwise a single value is assigned.
# NOTE: For assigning multiple variables, use 'upvars'.  Do NOT
#       use multiple 'upvar' calls, since one 'upvar' call might
#       reassign a variable to be used by another 'upvar' call.
# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
         fi
    fi
}

И используйте это так изнутри Newfun():

local "$1" && upvar $1 new

Для возврата нескольких переменных используйте другую вспомогательную функцию upvars. Это позволяет передавать несколько переменных в одном вызове, что позволяет избежать возможных конфликтов, если один вызов upvar изменяет переменную, используемую в другом последующем вызове upvar.

Смотрите: http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference для вспомогательной функции upvars и дополнительную информацию.

Проблема с:

eval $1=new

в том, что это не безопасно, если $1 содержит команду:

set -- 'ls /;true'
eval $1=new  # Oops

Было бы лучше использовать printf -v:

printf -v "$1" %s new

Но printf -v не может назначать массивы.

Более того, и eval, и printf не будут работать, если переменная объявлена ​​local:

g() { local b; eval $1=bar; }  # WRONG
g b                            # Conflicts with `local b'
echo $b                        # b is empty unexpected

Конфликт сохраняется, даже если local b равен unset:

g() { local b; unset b; eval $1=bar; }  # WRONG
g b                                     # Still conflicts with `local b'
echo $b                                 # b is empty unexpected
16
Freddy Vulto

Я нашел способ сделать это, но я не уверен, насколько это правильно:

Newfun()
{
    local var1="$1"
    eval $var1=2
    # or can do eval $1=2 if no local var
}

var=1
echo  var is $var    # $var = 1
newfun 'var'         # pass the name of the variable…
echo now var is $var # $var = 2

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

12
Roman M

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

boo() {
    eval ${1}="new"
}

SOME_VAR="old"
echo $SOME_VAR # old
boo "SOME_VAR"
echo $SOME_VAR # new

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

11
David Z

Итак, этот вопрос ждал «реального» решения в течение некоторого времени, и я рад сообщить, что теперь мы можем достичь этого, не используя eval вообще.

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

#!/bin/bash

# NOTE this does require a bash version >= 4.3

set -o errexit -o nounset -o posix -o pipefail

passedByRef() {

    local -n theRef

    if [ 0 -lt $# ]; then

        theRef=$1

        echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theRef}"

        # now that we have a reference, we can assign things to it

        theRef="some other value"

        echo -e "${FUNCNAME}:\n\tvalue of my reference set to:\n\t\t${theRef}"

    else

        echo "Error: missing argument"

        exit 1
    fi
}

referenceTester() {

    local theVariable="I am a variable"

    # note the absence of quoting and escaping etc.

    local -n theReference=theVariable

    echo -e "${FUNCNAME}:\n\tthe value of my reference is:\n\t\t${theReference}"

    passedByRef theReference

    echo -e "${FUNCNAME}:\n\tthe value of my reference is now:\n\t\t${theReference},\n\tand the pointed to variable:\n\t\t${theVariable}"

}

# run it

referenceTester
4
Aethalides
#!/bin/bash

append_string()
{
if [ -z "${!1}" ]; then
eval "${1}='$2'"
else
eval "${1}='${!1}''${!3}''$2'"
fi
}

PETS=''
SEP='|'
append_string "PETS" "cat" "SEP"
echo "$PETS"
append_string "PETS" "dog" "SEP"
echo "$PETS"
append_string "PETS" "hamster" "SEP"
echo "$PETS"

Результат:

cat
cat|dog
cat|dog|hamster

Структура для вызова этой функции:

append_string  name_of_var_to_update  string_to_add  name_of_var_containing_sep_char

Имя переменной передается в fuction о PETS и SEP, а добавляемая строка передается как значение обычным способом. «$ {! 1}» относится к содержимому глобальной переменной PETS. В начале эта переменная пуста и contens добавляется каждый раз, когда мы вызываем функцию. Символ разделителя может быть выбран по мере необходимости. стартовые строки "eval" обновляют переменную PETS.

2
ajaaskel

Eval никогда не должен использоваться в строке, которую пользователь может установить, потому что это опасно. Что-то вроде "string; rm -rf ~" будет плохим. Поэтому, как правило, лучше всего найти решения, в которых вам не о чем беспокоиться.

Однако eval понадобится для установки переданных переменных, как отмечено в комментарии.

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
2
Ian Kelling

Это то, что у меня работает на Ubuntu Bash Shell

#!/bin/sh

iteration=10

increment_count()
{
  local i
  i=$(($1+1))
  eval ${1}=\$i
}


increment_count iteration
echo $iteration #prints 11
increment_count iteration
echo $iteration #prints 12
0
enthusiasticgeek