web-dev-qa-db-ja.com

Bashの動的変数名

Bashスクリプトについて混乱しています。

私は次のコードを持っています:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

コマンドの最初の引数を含み、たとえば次の値を持つ変数名を作成できるようにします。 lsの最後の行。

だから私が欲しいものを説明するために:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

それでは、$magic_way_to_define_magic_variable_$1をどのように定義/宣言し、スクリプト内でそれを呼び出す必要がありますか?

eval${...}\$${...}を試しましたが、まだ混乱しています。

116
Konstantinos

キーとしてコマンド名を使用して、連想配列を使用します。

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

連想配列を使用できない場合(たとえば、bash 3をサポートする必要がある場合)、declareを使用して動的変数名を作成できます。

declare "magic_variable_$1=$(ls | tail -1)"

間接的なパラメーター展開を使用して値にアクセスします。

var="magic_variable_$1"
echo "${!var}"

BashFAQ: 間接指定-間接変数/参照変数の評価 を参照してください。

121
chepner

私は最近それを行うより良い方法を探していました。連想配列は私にとってはやり過ぎのように思えました。こんなものをみつけた:

suffix=bzz
declare prefix_$suffix=mystr

...その後...

varname=prefix_$suffix
echo ${!varname}
191
Yorik.sar

以下の例は、$ name_of_varの値を返します

var=name_of_var
echo $(eval echo "\$$var")
16
meow meow

連想配列以外にも、Bashで動的変数を実現する方法がいくつかあります。これらすべての手法にはリスクが伴うことに注意してください。リスクについては、この回答の最後で説明します。

次の例では、i=37と、初期値がlolilolであるvar_37という名前の変数のエイリアスを作成すると仮定します。

方法1.「ポインター」変数を使用する

Cポインターとは異なり、単純に変数の名前を間接変数に格納できます。 Bashには、エイリアス変数のreadingの構文があります。${!name}は、名前が変数nameの値である変数の値に展開されます。これは2段階の展開と考えることができます。${!name}$var_37に展開され、これはlolilolに展開されます。

name="var_$i"
echo "$name"         # outputs “var_37”
echo "${!name}"      # outputs “lolilol”
echo "${!name%lol}"  # outputs “loli”
# etc.

残念ながら、エイリアス変数のmodifyingに対応する構文はありません。代わりに、次のいずれかの方法で割り当てを達成できます。

1a。 evalで割り当てる

evalは悪ですが、目標を達成するための最も単純で最も移植性の高い方法でもあります。評価されるtwiceので、割り当ての右側を慎重にエスケープする必要があります。これを行う簡単で体系的な方法は、事前に右側を評価する(またはprintf %qを使用する)ことです。

そして左側が有効な変数名、またはインデックス付きの名前であることを手動で確認する必要があります(それがevil_code #だったらどうでしょう?)。対照的に、以下の他のすべての方法では自動的に強制されます。

# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit

value='babibab'
eval "$name"='$value'  # carefully escape the right-hand side!
echo "$var_37"  # outputs “babibab”

欠点:

  • 変数名の妥当性をチェックしません。
  • evalは悪です。
  • evalは悪です。
  • evalは悪です。

1b。 readで割り当てる

readビルトインを使用すると、名前を指定した変数に値を割り当てることができます。この事実は、ヒア文字列と組み合わせて利用できます。

IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37"  # outputs “babibab\n”

IFS部分とオプション-rは、値がそのまま割り当てられていることを確認し、オプション-d ''は複数行の値を割り当てることができます。この最後のオプションのため、コマンドはゼロ以外の終了コードで戻ります。

Here-stringを使用しているため、値に改行文字が追加されることに注意してください。

欠点:

  • やや不明瞭。
  • ゼロ以外の終了コードで戻ります。
  • 値に改行を追加します。

1c。 printfで割り当てる

Bash 3.1(2005年リリース)以降、printfビルトインは、名前が指定された変数に結果を割り当てることもできます。以前のソリューションとは対照的に、それは機能するだけであり、物事を回避したり、分割を防ぐなどの追加の努力は必要ありません。

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

欠点:

  • 移植性が低い(しかし、よく)。

方法2.「参照」変数を使用する

Bash 4.3(2014年リリース)以降、declareビルトインには、C++参照のように別の変数への「名前参照」である変数を作成するためのオプション-nがあります。方法1と同様に、参照にはエイリアス変数の名前が格納されますが、参照にアクセスするたびに(読み取りまたは割り当てのいずれかで)、Bashは間接参照を自動的に解決します。

さらに、Bashには、参照自体の値を取得するための特別で非常に複雑な構文があり、自分で判断してください:${!ref}

declare -n ref="var_$i"
echo "${!ref}"  # outputs “var_37”
echo "$ref"     # outputs “lolilol”
ref='babibab'
echo "$var_37"  # outputs “babibab”

これは、以下で説明する落とし穴を回避するものではありませんが、少なくとも構文を簡単にします。

欠点:

  • ポータブルではありません。

リスク

これらすべてのエイリアシング手法には、いくつかのリスクがあります。 1つ目は、(読み取りまたは割り当てのために)インダイレクションを解決するたびに任意のコードを実行することです。実際、var_37のようなスカラー変数名の代わりに、arr[42]のような配列添え字をエイリアスすることもできます。ただし、Bashは必要になるたびに角括弧の内容を評価するため、arr[$(do_evil)]のエイリアシングは予期しない効果をもたらします。その結果、の起源を制御する場合にのみこれらの手法を使用しますエイリアス

function guillemots() {
  declare -n var="$1"
  var="«${var}»"
}

arr=( aaa bbb ccc )
guillemots 'arr[1]'  # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]'  # writes twice into date.out
            # (once when expanding var, once when assigning to it)

2番目のリスクは、巡回エイリアスの作成です。 Bash変数はスコープではなく名前で識別されるため、(スコープを囲むスコープから変数をエイリアスすると考えながら)誤って自分自身にエイリアスを作成する可能性があります。これは特に、共通変数名(varなど)を使用している場合に発生する可能性があります。結果として、これらの手法は、エイリアス変数の名前を制御するときにのみ使用します

function guillemots() {
  # var is intended to be local to the function,
  # aliasing a variable which comes from outside
  declare -n var="$1"
  var="«${var}»"
}

var='lolilol'
guillemots var  # Bash warnings: “var: circular name reference”
echo "$var"     # outputs anything!

出典:

8
Maëlan

これは動作するはずです:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"
4
Jahid

これも機能します

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

あなたの場合

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val
3
k_vishwanath

うわー、ほとんどの構文は恐ろしいです!間接的に配列を参照する必要がある場合、いくつかの簡単な構文を使用した1つのソリューションを次に示します。

#!/bin/bash

foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]} ;
done ;

より単純な使用例については、 Advanced Bash-Scripting Guide で説明されている構文をお勧めします。

2
ingyhere

インデックス配列の場合、次のように参照できます。

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

連想配列も同様に参照できますが、-Aの代わりにdeclare-aスイッチを必要とします。

1
Walf

コマンドの最初の引数を含む変数名を作成できるようにしたい

script.shファイル:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

テスト:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

help evalによると:

引数をシェルコマンドとして実行します。


既に述べたように、Bash ${!var}間接展開も使用できますが、配列インデックスの取得はサポートしていません。


詳細や例については、 BashFAQ/006 Indirection about を確認してください。

evalを使用せずにPOSIXまたはBourneシェルでその機能を複製できるトリックはありません。これは安全に行うのが難しい場合があります。ですから、これはあなた自身のリスクで使用することを考慮してください

ただし、次の注意事項に従って、インダイレクションの使用を再検討する必要があります。

通常、bashスクリプトでは、間接参照はまったく必要ありません。一般的に、Bash配列を理解または知らない場合、または関数などの他のBash機能を完全に検討していない場合、人々はこれをソリューションとして検討します。

変数名または他のbash構文をパラメーター内に配置することは、より適切な解決策がある問題を解決するために、しばしば不適切に不適切な状況で行われます。これは、コードとデータの分離に違反するため、バグやセキュリティの問題に陥りやすくなります。インダイレクションを使用すると、コードの透明性が低下し、追跡が難しくなります。

0
kenorb

BashFAQ/006 に従って、間接変数を割り当てるために ここでは文字列構文readを使用できます:

function grep_search() {
  read "$1" <<<$(ls | tail -1);
}

使用法:

$ grep_search open_box
$ echo $open_box
stack-overflow.txt
0
kenorb