参照によってスクリプト関数に引数を渡すことができるかどうかを確認したいと思います。
つまり、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-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
を回避することです。
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
ヘルパー関数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
と宣言された場合、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をまったく使用せずにこれを達成できるようになったことを嬉しく思います。
覚えておくべき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
#!/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変数を更新します。
Evalは危険であるため、ユーザーが設定できる文字列には使用しないでください。 「string; rm -rf〜」のようなものは悪いでしょう。したがって、一般に、心配する必要がないソリューションを見つけるのが最善です。
ただし、コメントにあるように、渡された変数を設定するにはevalが必要です。
$ y=four
$ four=4
$ echo ${!y}
4
$ foo() { x=$1; echo ${!x}; }
$ foo four
4
これは私にとって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