Bashの動的変数名


159

私は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${...}\$${...}、私はまだ混乱しています。


3
しないでください。連想配列を使用して、コマンド名をデータにマップします。
chepner 2013年

3
VAR = A; VAL = 333; "$ VAR" <<< "$ VAL"を読み取ります。エコー "A = $ A"
グリゴリーK

回答:


150

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

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

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

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

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

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

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

BashFAQ:Indirection-Evaluating indirect / reference variablesを参照してください


5
@DeaDEnD -aは、連想配列ではなく、インデックス付き配列を宣言します。への引数grep_searchが数値でない限り、それは数値を持つパラメーターとして扱われます(パラメーターが設定されていない場合、デフォルトで0になります)。
chepner 2013

1
うーん。私はbash 4.2.45(2)を使用していて、declareはそれをオプションとしてリストしていませんdeclare: usage: declare [-afFirtx] [-p] [name[=value] ...]。ただし、正常に動作しているようです。
フェント

declare -h4.2.45(2)では、私が示していますdeclare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]。3.2ではなく実際に4.xを実行していることを再確認する場合があります。
chepner 2013

5
なぜdeclare $varname="foo"ですか?
ベンデイビス

1
${!varname}ははるかにシンプルで広く互換性があります
Brad Hein

227

最近、もっと良い方法を探しています。連想配列は私にはやり過ぎのように聞こえました。私が見つけたものを見て:

suffix=bzz
declare prefix_$suffix=mystr

...その後...

varname=prefix_$suffix
echo ${!varname}

関数内でグローバルを宣言する場合は、bash> = 4.2で「declare -g」を使用できます。以前のbashでは、後で値を変更したくない限り、「declare」の代わりに「readonly」を使用できます。構成や何が問題ないのか。
サムワトキンス

7
カプセル化された変数形式を使用するのが最適ですprefix_${middle}_postfix(つまり、書式設定が機能しませんvarname=$prefix_suffix
msciwoj '12

1
私はbash 3に悩まされ、連想配列を使用できませんでした。そのため、これは命の恩人でした。$ {!...}その上でグーグルするのは簡単ではありません。var名を展開するだけだと思います。
Neil McGill

10
@NeilMcGill:参照してください「男のbashの」gnu.org/software/bash/manual/html_node/...:パラメータ展開の基本的な形は、$ {}のパラメータです。<...>パラメータの最初の文字が感嘆符(!)の場合、変数の間接参照のレベルが導入されます。Bashは、残りのパラメーターから形成された変数の値を変数の名前として使用します。次に、この変数が展開され、その値がパラメーター自体の値ではなく、残りの置換で使用されます。
Yorik.sar

1
@syntaxerror:上記の「declare」コマンドを使用して、好きなだけ値を割り当てることができます。
Yorik.sar

48

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

次の例では、初期値がであるi=37という名前の変数にエイリアスを設定することを想定しています。var_37lolilol

方法1.「ポインター」変数の使用

Cポインターとは異なり、変数の名前を間接変数に格納するだけです。次に、Bashにはエイリアス変数を読み取るための構文${!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.

残念ながら、エイリアスされた変数を変更するための対応する構文はありません。代わりに、次のいずれかの方法で割り当てを行うことができます。

1a。割り当てeval

evalは悪ですが、私たちの目標を達成するための最も簡単で移植可能な方法でもあります。割り当ての右側は2回評価されるため、慎重にエスケープする必要があります。これを行う簡単で体系的な方法は、事前に右側を評価する(またはを使用する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 ''複数行の値を割り当てることができます。この最後のオプションのため、コマンドはゼロ以外の終了コードで戻ります。

ここではヒア文字列を使用しているため、値に改行文字が追加されることに注意してください

欠点:

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

1c。割り当てprintf

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

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

欠点:

  • 移植性が低い(ただし、まあ)。

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

Bash 4.3(2014年にリリース)以降、declare組み込みには、-nC ++参照のように、別の変数への「名前参照」である変数を作成するオプションがあります。方法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!

ソース:


1
特にこの${!varname}手法ではの中間変数が必要になるため、これが最良の答えですvarname
RichVel

この答えが高く賛成されていないことを理解するのは難しい
マルコス

18

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

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

4
echoコマンドの置換(引用符のない)で2つのをネストする必要はありません。さらに、オプション-nをに与える必要がありますecho。そして、いつものように、eval安全ではありません。しかし、Bashにはこの目的のために、より安全で明確で短い構文があるため、これらすべてが不要です${!var}
Maëlan

4

これはうまくいくはずです:

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

これも機能します

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

使用する declare

他の回答のようにプレフィックスを使用する必要はなく、配列も必要ありません。ジャストdeclare二重引用符、およびパラメータ展開使用します

私はよく次のトリックを使用しone to nkey=value otherkey=othervalue etc=etc、のようにフォーマットされた引数を含む引数リストを解析します。

# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
  declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja 
# foo bar tip

しかし、次のようにargvリストを拡張する

for v in "$@"; do declare "${v%=*}=${v#*=}"; done

追加のヒント

# parse argv's leading key=value parameters
for v in "$@"; do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
  shift
done

1
これは非常にクリーンなソリューションのように見えます。邪悪なよだれかけやボブは使用せず、変数に関連するツールを使用します。たとえば、printforのように一見無関係または一見危険な関数も不明瞭にしないeval
kvantour

2

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

#!/bin/bash

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

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

より簡単な使用例では、高度なBashスクリプトガイドに記載されている構文をお勧めします


2
ABSは、悪い例をその例で紹介することで有名です。代わりにbash-hackers wikiまたはWooledge wikiを利用することを検討してください。代わりに、トピックに直接トピックBashFAQ#6があります。
Charles Duffy

@CharlesDuffy:ありがとう、これらの参照を見ていきます。ここでの構文が自分で思いついたか、どこか別の例からハッキングしたことは確かです。ABSへの私の言及は、動的変数名を使用しないなど、より単純なユースケースを探している人々のためのものでした。誰かが配列を参照したいだけでこの答えを見つけた場合、これはどれほど混乱するかと思いました。
ingyhere 2017

2
これは、内のエントリ場合にのみ機能foo_1とは、foo_2空白や特殊記号を含みません。問題のあるエントリの例:'a b'内に2つのエントリが作成されますmine''内にエントリを作成しませんmine'*'作業ディレクトリのコンテンツに展開されます。あなたは、引用することによって、これらの問題を防ぐことができます:eval 'mine=( "${foo_'"$i"'[@]}" )'
Socowi

@Socowiこれは、BASHの配列をループする際の一般的な問題です。これは、IFSを一時的に変更することで解決できます(もちろん、元に戻します)。引用がうまくいったのは良いことです。
-ingyhere

@ingyhere私は違うと頼みました。一般的な問題ではありません。標準的な解決策があります:常に[@]構造を引用します。"${array[@]}"単語の分割やの展開などの問題なしに、常に正しいエントリリストに展開されます*。また、単語分割の問題はIFS、配列内に表示されないnull以外の文字を知っている場合にのみ回避できます。またの*設定では文字通りの扱いはできませんIFSIFS='*'星で設定して分割するか、設定IFS=somethingOtherして*拡大します。
ソコウィ

1

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

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

使用しているシェル/ bashのバージョンに依存しない追加の方法は、を使用することenvsubstです。例えば:

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

0

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

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

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


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


詳細または例については、BashFAQ / 006でインダイレクションについて確認してください。

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

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

通常、bashスクリプトでは、間接参照はまったく必要ありません。一般的に、Bash配列について理解または知らない場合、または関数などの他のBash機能を十分に考慮していない場合、解決策としてこれを検討します。

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.