「変数名」を関数に渡し、そのような「変数名」を含む変数に含まれる値を関数に変換させ、変換されたオブジェクトを元の「変数名」で参照できるようにしています。
たとえば、区切りリストを配列に変換する関数があり、「animal_list」という名前の区切りリストがあるとします。リスト名を関数に渡し、現在の配列を「animal_list」として参照することで、そのリストを配列に変換したいと思います。
コード例:
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim";
temp_array=($list);
IFS=$oifs;
# Now I have the list converted to an array but it's
# named temp_array. I want to reference it by its
# original name.
}
# ----------------------------------------------------
animal_list="anaconda, bison, cougar, dingo"
delim_to_array ${animal_list} ","
# After this point I want to be able to deal with animal_name as an array.
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
# And reuse this in several places to converted lists to arrays
people_list="alvin|baron|caleb|doug"
delim_to_array ${people_list} "|"
# Now I want to treat animal_name as an array
for person in "${people_list[@]}"; do
echo "NAME: $person"
done
これを理解するには少し努力が必要です。我慢して。ソリューションはbashで正しく動作します。いくつかの「バシム」が必要です。
まず、変数_${!variable}
_への「間接」アクセスを使用する必要があります。 _$variable
_に文字列_animal_name
_が含まれている場合、「パラメータ拡張」:_${!variable}
_は_$animal_name
_の内容に拡張されます。
そのアイデアを実際に見てみましょう。理解しやすくするために、可能な限り使用した名前と値を保持しています。
_#!/bin/bash
function delim_to_array() {
local VarName=$1
local IFS="$2";
printf "inside IFS=<%s>\n" "$IFS"
echo "inside var $VarName"
echo "inside list = ${!VarName}"
echo a\=\(${!VarName}\)
eval a\=\(${!VarName}\)
printf "in <%s> " "${a[@]}"; echo
eval $VarName\=\(${!VarName}\)
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "out <%s> " "${animal_list[@]}"; echo
printf "outside IFS=<%s>\n" "$IFS"
# Now we can use animal_name as an array
for animal in "${animal_list[@]}"; do
echo "NAME: $animal"
done
_
その完全なスクリプトが実行された場合(その名前付きso-setvar.shを想定しましょう)、次のように表示されます。
_$ ./so-setvar.sh
inside IFS=<,>
inside var animal_list
inside list = anaconda, bison, cougar, dingo
a=(anaconda bison cougar dingo)
in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
outside IFS=<
>
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
_
「内部」は「機能の内部」を意味し、「外部」はその反対を意味することを理解してください。
_$VarName
_内の値は、文字列としての変数名__animal_list
_です。
_${!VarName}
_の値はリストであることが示されています:_anaconda, bison, cougar, dingo
_
ここで、ソリューションがどのように構築されるかを示すために、echoを含む行があります。
_echo a\=\(${!VarName}\)
_
これは、eval
を含む次の行の実行内容を示しています。
_a=(anaconda bison cougar dingo)
_
eval uatedになると、変数a
は動物のリストを含む配列になります。この例では、変数aを使用して、評価がどのように影響するかを正確に示しています。
次に、a
の各要素の値が_<in> val
_として出力されます。
そして、関数の外側の部分でも同じことが_<out> val
_として実行されます
それはこの2行に示されています:
_in <anaconda> in <bison> in <cougar> in <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>
_
実際の変更は関数の最後の評価で実行されたことに注意してください。
以上です。これで、varに値の配列があります。
実際、関数のコアは1行です:eval $VarName\=\(${!VarName}\)
また、IFSの値は関数に対してローカルに設定されるため、追加の作業なしで関数を実行する前の値に戻ります。元のアイデアに関するコメントを寄せてくれた Peter Cordes に感謝します。
これで説明は終わりです。明確にしてください。
不要な行をすべて削除してコアevalのみを残し、IFSの新しい変数のみを作成する場合、関数を最小限の式に減らします。
_delim_to_array() {
local IFS="${2:-$' :|'}"
eval $1\=\(${!1}\);
}
_
IFSの値をローカル変数として設定すると、関数の「デフォルト」値も設定できます。 IFSに必要な値が2番目の引数として関数に送信されない場合は常に、ローカルIFSは「デフォルト」値を取ります。デフォルトは space ()(これは常に有用な分割値です)、 colon (:)、 そしてその vertical line (|)。これらの3つはいずれも値を分割します。もちろん、デフォルトは、ニーズに合う他の値に設定できます。
read
を使用するように編集:Evalで引用符で囲まれていない値のリスクを減らすために、次を使用できます。
_delim_to_array() {
local IFS="${2:-$' :|'}"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}"
}
test="fail-test"; a="fail-test"
animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*'
delim_to_array "animal_list" ","
printf "<%s>" "${animal_list[@]}"; echo
_
_$ so-setvar.sh
<bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
_
上記の変数_animal_list
_に設定された値のほとんどは、evalで失敗します。
しかし、問題なく読み取りを渡します。
eval
の一般的な使用に対する検証ではありません。この関数が何をどのように機能するかを本当に理解するために、この関数を使用して投稿したコードを書き直しました。
_#!/bin/bash
delim_to_array() {
local IFS="${2:-$' :|'}"
# printf "inside IFS=<%s>\n" "$IFS"
# eval $1\=\(${!1}\);
read -ra "$1" <<<"${!1}";
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\t " "${animal_list[@]}"; echo
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list"
printf "NAME: %s\t " "${people_list[@]}"; echo
_
_$ ./so-setvar.sh
NAME: anaconda NAME: bison NAME: cougar NAME: dingo
NAME: alvin NAME: baron NAME: caleb NAME: doug
_
ご覧のとおり、IFSは関数内でのみ設定され、永続的に変更されるわけではないため、古い値に再設定する必要はありません。さらに、関数の2番目の呼び出し "people_list"はIFSのデフォルト値を利用するため、2番目の引数を設定する必要はありません。
(eval)関数が作成されたので、varが引用されずにシェル解析に公開される場所が1つあります。これにより、IFS値を使用して「単語分割」を実行できます。しかし、それはvarsの値を公開します(引用符でそれを妨げない限り)。注文。そして、それをサポートするシステムでのプロセス置換<() >()
。
それぞれの例(最後を除く)は、この単純なエコーに含まれています(注意してください)。
_ a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*
_
つまり、_{~$`<>
_で始まるか、ファイル名と一致するか、または_?*[]
_を含む文字列は、潜在的な問題です。
変数にそのような問題のある値が含まれていないことが確実であれば、安全です。そのような値を持つ可能性がある場合、質問に答える方法はより複雑になり、より多くの(さらに長い)説明と説明が必要になります。 read
を使用することもできます。
はい、read
には、独自の"dragons"が付属しています。
read
コマンドは1行しか取得できませんでした。 _-d
_オプションを設定しても、複数行には特別な注意が必要です。または、入力全体が1つの変数に割り当てられます。IFS
値にスペースが含まれている場合、先頭と末尾のスペースは削除されます。まあ、完全な説明にはtab
についての詳細を含める必要がありますが、スキップします。|
_データを読み取らないでください。行う場合、読み取りはサブシェルになります。サブシェルで設定されたすべての変数は、親シェルに戻ると保持されません。まあ、いくつかの回避策がありますが、ここでも、詳細はスキップします。警告や読み取りの問題を含めるつもりはありませんでしたが、一般的なリクエストにより、申し訳ありませんでした。
The Bash FAQは、参照/間接参照による呼び出しに関するエントリ全体を持っています 。
単純なケースでは、他の回答で提案されているeval
のより良い代替手段であり、引用符muchがより簡単になります。
func() { # set the caller's simple non-array variable
local retvar=$1
printf -v "$retvar" '%s ' "${@:2}" # concat all the remaining args
}
Bash-completion (タブを押したときに実行されるコード)は、その内部関数がeval
ではなくprintf -v
に切り替わりました。
配列を返す場合、 Bash FAQ はread -a
を使用して配列変数のシーケンシャル配列インデックスを読み取ることをお勧めします。
# Bash
aref=realarray
IFS=' ' read -d '' -ra "$aref" <<<'words go into array elements'
Bash 4.3では、参照による呼び出しを大幅に便利にする機能が導入されました。 Bash 4.3はまだ新しい(2014)。
func () { # return an array in a var named by the caller
typeset -n ref1=$1 # ref1 is a nameref variable.
shift # remove the var name from the positional parameters
echo "${!ref1} = $ref1" # prints the name and contents of the real variable
ref1=( "foo" "bar" "$@" ) # sets the caller's variable.
}
Bashのmanページの表現は少し混乱していることに注意してください。これは、-n
属性を配列変数に適用できないことを示しています。つまり、参照の配列は使用できませんが、配列への参照を使用できます。
関数の内容のみを渡すため、関数内の変数(この場合は配列)を変更することはできません。関数は、渡された変数を認識していません。
回避策として、変数のnameを渡し、関数の内部でfunction eval
uateを使用してコンテンツを取得できます。
#!/bin/bash
function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim"
temp_array=($(eval echo '"${'"$list"'}"'))
IFS=$oifs;
eval "$list=("${temp_array[@]}")"
}
animal_list="anaconda, bison, cougar, dingo"
delim_to_array "animal_list" ","
printf "NAME: %s\n" "${animal_list[@]}"
people_list="alvin|baron|caleb|doug"
delim_to_array "people_list" "|"
printf "NAME: %s\n" "${people_list[@]}"
eval
が使用されている行の引用符に細心の注意を払ってください。式の一部は一重引用符で囲む必要があり、他の部分は二重引用符で囲む必要があります。さらに、最終的な印刷では、for
ループをより単純なprintf
コマンドに置き換えました。
出力:
NAME: anaconda
NAME: bison
NAME: cougar
NAME: dingo
NAME: alvin
NAME: baron
NAME: caleb
NAME: doug
_function delim_to_array() {
local list=$1
local delim=$2
local oifs=$IFS;
IFS="$delim";
temp_array=($list);
IFS=$oifs;
}
_
したがって、この関数で非常に単純な詳細をスキップしていると思います。呼び出し先が繰り返し処理のみを実行し、呼び出し元がショットを呼び出すと、常に簡単になります。その関数では、呼び出し先にすべての呼び出しを行わせています。これらの名前をそのように処理する必要はありません。
_isName()
case "${1##[0-9]*}" in
(${IFS:+*}|*[!_[:alnum:]]*)
IFS= "${IFS:+isName}" "$1"|| ! :
esac 2>/dev/null
setSplit(){
isName "$1" ||
set "" "setSplit(): bad name: '$1'"
eval "shift; set -f
${1:?"$2"}=(\$*)
set +f -$-"
}
_
これは、配列名を安全に検証し、stderrに意味のあるエラー出力を生成し、無効な引数で呼び出されたときに適切に終了を停止します。エラー出力は次のようになります。
_bash: 1: setSplit(): bad name: 'arr@yname'
_
...ここでbash
はシェルの現在の_$0
_であり、_arr@yname
_はsetSplit()
の最初の引数でした。私はそれを呼んだ、そしてそれはそのメッセージを書いた。
これは2つの関数でもあります。したがって、呼び出し側はisName()
関数をまったく変更せずに、その裁量でsetSplit()
のテストを動的に再定義できます。
また、シェルのファイル名生成グロブを安全に無効化して、分割中の不注意による展開を防ぎます。デフォルトでは、引数に文字_[*?
_が含まれている場合にそうなる可能性があります。シェルオプションを返す前に、シェルオプションを復元したときに、それらを見つけた状態に変更した可能性があります。つまり、シェルのファイル名のグロビングを有効または無効にして呼び出すことができ、その設定に影響を与えることはありません。 。
ただし、ここで欠落している重要な点があります。_$IFS
_は構成されていません。 isName()
関数は、bash
パターン内のPOSIXブラケット式の内容に_$IFS
_を適用するというかなり警戒すべきcase
バグの回避策を実装します(真剣に:一体何ですか?単一の自己再帰呼び出しでローカル_$IFS
_を無効にし、グローバル値が戻っていない場合。しかし、それは配列の分割と完全に直交しています。それ以外の場合、setSplit()
は_$IFS
_を使用して何も実行しません。そして、それは本来あるべき姿です。そのようにする必要はありません。
callerは次のように設定する必要があります。
_IFS=aBc setSplit arrayname 'xyzam*oBabc' x y z
printf '<%q>\n' "$IFS" "${arrayname[@]}"
_
_<$' \t\n'>
<xyz>
<m\*o>
<''>
<b>
<''>
<x>
<y>
<z>
_
上記はbash
シェルで機能し、呼び出された関数にローカルの_$IFS
_値を設定します。
POSIXly:
_IFS=aBc command eval "setSplit arrayname 'xyzam*oBabc' x y z"
_
...同じ目的を果たします。違いは、特別な組み込み関数や関数の永続的な環境に関するbash
の標準との違いにあります。それ以外の場合は、コマンドラインで設定された変数が現在のシェル環境に影響を与えることを指定します(したがって、どちらの方法でも使用できるという点で推奨されます)。
好みが何であれ、重要なのは、発信者がここでショットを呼び出し、着信者がシュートするだけです。