52

参照によってスクリプト関数に引数を渡すことが可能かどうかを尋ねたい:

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

任意の考えをいただければ幸いです。

4

9 に答える 9

25

Bash の man ページから (パラメーター展開):

    パラメータの最初の文字が感嘆符 (!) の場合、
    可変インダイレクションのレベルが導入されました。Bash は次の値を使用します。
    パラメータの残りの部分から形成された変数を
    変数; 次に、この変数が展開され、その値が使用されます
    パラメータの値ではなく、残りの置換
    自体。これは間接展開として知られています。

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

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

$ a=1 b=2
$ swap a b
$ echo $a $b
2 1
于 2013-02-13T11:25:44.847 に答える
17

ヘルパー関数を使用します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ます。

ヘルパー関数と詳細については、 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しません。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
于 2010-05-17T20:19:37.280 に答える
14

私はこれを行う方法を見つけましたが、これがどれほど正しいかわかりません:

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...を使用します。

于 2009-02-12T07:24:29.830 に答える
12

Bashには参照のようなものは組み込まれていないため、基本的に、変更したいグローバル変数の名前を関数に渡すことが唯一の方法です。evalそれでも、ステートメントが必要になります。

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

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

Bashは間接参照に名前が格納されている変数の値に自動的にアクセスするため、ここで間接参照を使用できるとは思いません。それはあなたにそれを設定する機会を与えません。

于 2009-02-12T07:57:03.903 に答える
6

さて、この質問はしばらくの間「実際の」解決策を待っていましたが、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}"
    
}

実行時の出力:

referenceTester:
        the value of my reference is:
                I am a variable
passedByRef:
        the value of my reference is:
                I am a variable
passedByRef:
        value of my reference set to:
                some other value
referenceTester:
        the value of my reference is now:
                some other value,
        and the pointed to variable:
                some other value
于 2016-09-22T16:33:47.250 に答える
2

Eval は危険なので、ユーザーが設定できる文字列には使用しないでください。「string; rm -rf ~」のようなものはまずいでしょう。したがって、一般的には、心配する必要のない解決策を見つけるのが最善です。

ただし、コメントに記載されているように、渡された変数を設定するには eval が必要です。

$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
于 2009-02-12T21:32:24.583 に答える