Вопрос

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

то естьсделать что-то, что на C++ будет выглядеть так:

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

int main() {
    int t = 4;
    printf("%d\n", t); // t->4
    boo(t);
    printf("%d\n", t); // t->5
}

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

function boo () 
{

    var1=$1       # now var1 is global to the script but using it outside
                  # this function makes me lose 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

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

Это было полезно?

Решение

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

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

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

Другие советы

На странице руководства Bash (расширение параметров):

    If the first  character of parameter is an exclamation  point (!), a
    level of variable indirection is  introduced. Bash uses the value of
    the variable  formed from the rest  of parameter as the  name of the
    variable; this variable  is then expanded and that value  is used in
    the rest  of the  substitution, rather than  the value  of parameter
    itself. This is known as indirect expansion.

Следовательно, ссылка — это имя переменной.Вот swap Функция с использованием переменной. Крайнация, которая не требует временной переменной:

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

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1

Используйте вспомогательную функцию 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

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

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...

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

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

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

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

Итак, этот вопрос уже некоторое время ждет «реального» решения, и я рад сообщить, что теперь мы можем сделать это вообще без использования 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

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

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

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
#!/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

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

Это то, что у меня работает в оболочке Ubuntu bash

#!/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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top