説明する

説明する

「var name」を関数に渡し、関数が「var name」を使用して変数に含まれる値を変換し、変換されたオブジェクトを元の「var name」として参照できるようにします。

たとえば、区切りリストを配列に変換する関数があり、「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

ベストアンサー1

説明する

これを理解するには少し努力が必要です。忍耐を持ってください。このソリューションは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

「内部」は「関数の内部」を意味し、「外部」はその逆を意味することを理解してください。

内部値は文字列の$VarNamevar:の名前です。animal_list

値は${!VarName}リストとして表示されます。anaconda, bison, cougar, dingo

これで、ソリューションがどのように構築されたかを示すために、echoを含む行があります。

echo a\=\(${!VarName}\)

eval次の行が何をするかを示します。

a=(anaconda  bison  cougar  dingo)

一度評価するはい、変数はa動物のリストを含む配列です。この場合、var a は、eval がどのように影響するかを正確に示すために使用されます。

その後、各要素の値がa印刷されます<in> val。そして、次の2行に示す
ように、関数の外側の部分でも同じことを行います。<out> val

in  <anaconda> in  <bison> in  <cougar> in  <dingo>
out <anaconda> out <bison> out <cougar> out <dingo>

実際の変更は、関数の最後の評価で行われます。
それはすべてです。 varには現在値配列があります。

実際、この関数の中心は1行です。eval $VarName\=\(${!VarName}\)

さらに、IFSの値は関数のローカル値に設定されているため、追加の操作なしで関数が実行される前の値に戻ることができます。ありがとうピーター・コルデス独創的なアイデアについての意見です。

これで説明が終わりました。明確になりますように。


実際の機能

不要な行をすべて削除し、コア評価のみを残し、IFSの新しい変数のみを作成すると、関数を最小限の表現に減らします。

delim_to_array() {
    local IFS="${2:-$' :|'}"
    eval $1\=\(${!1}\);
}

IFS値をローカル変数に設定すると、関数の「デフォルト」値を設定することもできます。 IFSが要求する値が2番目のパラメーターとして関数に送信されない限り、ローカルIFSは「デフォルト」値を想定します。デフォルトはspace()(常に有用な分割値です)、colon(:)、vertical line(|)でなければならないと思います。これら3つのうちの1つは値を分割します。もちろん、デフォルト値は必要に応じて異なる値に設定できます。

使用するように編集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")><./*><*><*>

上記のvarに設定されたほとんどの値はanimal_listevalで失敗します。
しかし、問題なく読んでください。

  • 注: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は関数内でのみ設定され、永久に変更されないため、以前の値にリセットする必要はありません。さらに、「people_list」関数への2番目の呼び出しは、2番目のパラメータを設定せずにIFSのデフォルト値を利用します。


«ここに龍がある» ́\_(ツ)_/́


警告01:

(eval)関数を設定すると、varが引用符なしでシェル解析に公開される点があります。これにより、IFS値を使用して「単語の分割」を完了できます。しかし、これはまた、varの値を「中括弧拡張」、「チルダ拡張」、「引数、変数および算術拡張」、「コマンド置換」、および「パス名拡張」に公開します。ここでコマンドは次のようになります。<() >()これをサポートするシステムでプロセス交換を実行します。

すべての例(最後の例を除く)は、次の単純なエコーでラップされます(注意してください)。

 a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*

つまり、ファイル{~$`<>名で始まる、ファイル名と一致する、またはファイル名を含めることができる文字列は、潜在的な問題になる?*[]可能性があります。

変数にそのような問題のある値が含まれていないと確信している場合は安全です。そのような値を持つことが可能であれば、あなたの質問に答える方法はより複雑で、より多くの(またはより長い)説明と説明が必要です。使用はread代替品です。

警告02:

はい、独自のread「ドラゴン」があります。

  • 常に-rオプションを使用してください。このオプションが不要な状況は考えられません。
  • このreadコマンドは1行だけインポートできます。オプションを設定しても複数行を使用するには-d特別な注意が必要です。または、入力全体を変数に割り当てます。
  • 値にスペースが含まれている場合は、先行スペースと末尾のIFSスペースが削除されます。まあ、完全な説明にはそれに関するいくつかの詳細を含める必要がありますtabが、省略します。
  • パイプを介して|データを読み取らないでください。これにより、サブシェルから読み取りが行われます。子シェルに設定されたすべての変数は、親シェルに戻っても保持されません。まあ、いくつかの回避策がありますが、再び詳細はスキップします。

もともと読書に関する警告や問題を含めたくはありませんでしたが、一般のニーズに合わせて含める必要がありました。申し訳ありません。

おすすめ記事