bashおよびzshの二重および三重置換


22

この質問の背景部分へのフォローアップ。

で、二重置換にbash使用できます。両方で、旧式(hack-y)が機能します。${!FOO}zsh ${(P)FOO}eval \$$FOO

したがって、私にとって最も賢明で最も論理的なことは${${FOO}}, ${${${FOO}}}…、double / triple / n置換です。なぜこれが期待どおりに機能しないのですか?

第二:何をするん\で行うevalの文?私はそれが脱出であり、eval \$$$FOO不可能なことをしていると考えています。すべてのシェルで動作するトリプル/ n置換を行う方法は?

回答:


15

\膨張防止するために使用されなければならない$$(現在のプロセスID)。トリプル置換の場合、ダブルevalが必要です。したがって、各evalでの不要な展開を回避するために、さらにエスケープする必要があります。

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4

まだ:l3=l2; eval eval eval echo \\\$\\$\$$l353294だから正確にモジュラーではない。
-Profpatsch

2
なんでこんなことができないのeval $(eval echo \$$l2)?私はbashが嫌いです、それは全く意味がありません。
-Profpatsch

@Profpatsch:さらに例を追加しました。
チョロバ

ああ、それはn²です。このような理由を知りたくはありません。
-Profpatsch

2
@Profpatsch:簡単です。各ステップで、バックスラッシュの数は半分になります(実際、2 isです)。
チョロバ


6

の値がFOO有効な変数名(BAR)でeval \$$FOOある場合、の値BARを個別の単語に分割し、各単語をワイルドカードパターンとして扱い、結果の最初の単語をコマンドとして実行し、他の単語を引数として渡します。ドル記号の前にあるバックスラッシュは、ドル記号を文字どおりに処理するため、ビルトインに渡される引数evalは4文字の文字列$BARです。

${${FOO}}構文エラーです。一般的なシェルにはこのような機能がないため、「二重置換」を行いません(とにかくこの構文ではありません)。zshのでは、${${FOO}} ある有効で、二重置換であるが、それはあなたが好きなものとは異なる動作をします:それはの値に2つの連続した変換を行いFOO、恒等変換され、どちらもが、それは執筆のちょうど空想の方法です${FOO}

変数の値を変数として扱いたい場合、適切に引用することに注意してください。結果を変数に設定すると、はるかに簡単になります。

eval "value=\${$FOO}"

1
最初の単語がコマンドとして使用されないように、変数として設定しますか?男、この種の論理は理解するのが難しい。それは私がbashで抱えている一般的な問題です。
-Profpatsch

4

{ba,z}sh 溶液

両方で機能する関数を次に示し{ba,z}shます。POSIXにも準拠していると思います。

クォートで狂わずに、次のような多くのレベルの間接指定に使用できます。

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

または、より多くの(!?!)レベルの間接参照がある場合は、ループを使用できます。

与えられたときに警告します:

  • ヌル入力
  • 設定されていない変数名

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

実用的なクロスシェルソリューション。「POSIX互換の二重変数展開」が将来この答えにリダイレクトされることを願っています。
BlueDrink9

2

${$foo}inの代わりに動作しないよう${(P)foo}zsh、どちらも動作しません${${$foo}}。インダイレクションの各レベルを指定するだけです:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

もちろん、${!${!foo}}中には動作しませんbashので、bashネストされた置換を許可していません。


おかげで、それは知っておくと良いことです。これはzsh専用であるため、実際にはスクリプトに入れることはできません(誰もがbashを持っていますが、その逆ではありません)。
-Profpatsch

zshはあなたが想像するよりもはるかに頻繁にインストールされていると思います。これは、にリンクされないことがsh好きですbash(常にではない)ことが多いですが、それはまだ、シェルスクリプトのために利用できます。PerlやPythonを実行するように考えてください。
-chepner

2

なぜそれをする必要があるのですか?

次のようないくつかの手順でいつでも実行できます。

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

または、次のような関数を使用します。

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

次に:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIWでzsh

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah

ええ、いくつかのステップを使用することは今まで私には起こりませんでした…奇妙です。
-Profpatsch

私の観点から、@ Profpatschがそれをたい理由は明らかです。それが最も直感的な方法だからです。ビーイングハード bashで複数の置換を実施することは別のものです。
ニコスアレクサン

2

POSIXソリューション

クォートで狂わずに、次のように多くのレベルの間接化にこの関数を使用できます。

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

または、より多くの(!?!)レベルの間接参照がある場合は、ループを使用できます。

与えられたときに警告します:

  • ヌル入力
  • 複数の引数
  • 設定されていない変数名

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

この回答を以前の回答と組み合わせていない理由はありますか?一見すると、両者の違いはわかりません。
BlueDrink9
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.