web-dev-qa-db-ja.com

参照による引数の受け渡し

参照によってスクリプト関数に引数を渡すことができるかどうかを確認したいと思います。

つまり、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

どんな考えでもいただければ幸いです。

41
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_、not_boo $SOME_VAR_

  • 関数内で、_local -n ref=$1_を使用して_$1_で指定された変数にnamerefを宣言します。つまり、それは_$1_自体への参照ではなく、変数その名前 _$1_が保持するもの、つまりこの場合は_SOME_VAR_への参照です。右側の値は、既存の変数を示す文字列にする必要があります。文字列の取得方法は関係ないため、_local -n ref="my_var"_やlocal -n ref=$(get_var_name)なども機能します。 declareは、それを許可/要求するコンテキストでlocalを置き換えることもできます。詳細については、「 Bashリファレンスマニュアルのシェルパラメータの章 」を参照してください。

このアプローチの利点は、(間違いなく)可読性が向上することであり、最も重要なのは、セキュリティの落とし穴が多数あり、十分に文書化されているevalを回避することです。

24

Bashのマンページ(パラメーターの拡張)から:

パラメータの最初の文字が感嘆符(!)の場合、変数間接参照の
レベルが導入されます。 Bashは
の値として、残りのパラメーターから形成された変数を
変数の名前として使用します。次に、この変数が展開され、その値がパラメーター
自体の値ではなく、残りの置換で使用されます。これは間接展開と呼ばれます。

したがって、参照は変数の名前です。次に、一時変数を必要としない変数の間接参照を使用するswap関数を示します。

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

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

ヘルパー関数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を使用します。これにより、1回の呼び出しで複数の変数を渡すことができるため、1つのupvar呼び出しが別のupvar呼び出しで使用される変数を変更した場合に発生する可能性のある競合を回避できます。

ヘルパー関数upvarsおよび詳細については、 http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference を参照してください。

の問題:

_eval $1=new
_

_$1_にコマンドが含まれていると安全ではないということです。

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

_printf -v_を使用することをお勧めします。

_printf -v "$1" %s new
_

ただし、_printf -v_は配列を割り当てることができません。

さらに、変数がevalと宣言された場合、printflocalはどちらも機能しません。

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

14
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をまったく使用せずにこれを達成できるようになったことを嬉しく思います。

覚えておくべきkeyは、少なくとも私の例では、呼び出し元と呼び出し先の両方で参照を宣言することです。

#!/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
6
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

変数の名前はPETSについてfuctionに渡され、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
1