web-dev-qa-db-ja.com

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

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

bashでは、zsh${(P)FOO}で二重置換に${!FOO}を使用できます。どちらも、旧式(hack-y)のeval \$$FOOが機能します。

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

2番目:evalステートメントで\は何をしますか?それは脱出だと思い、eval \$$$FOOのようなものを不可能にします。すべてのシェルで機能するトリプル/ n置換をどのように行うのですか?

24
Profpatsch

\は、$$(現在のプロセスID)の展開を防ぐために使用する必要があります。三重置換の場合、二重評価が必要になるため、各評価での不要な展開を回避するためにエスケープを増やす必要があります。

#! /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
16
choroba
#!/bin/bash

hello=world
echo=hello

echo $echo ${!echo}
10

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

${${FOO}}は構文エラーです。いずれの一般的なシェルにもこのような機能がないため(この構文ではありません)、「二重置換」は行われません。 zshでは、${${FOO}}isは有効であり、二重置換ですが、これは希望する動作とは異なります:FOOの値に対して2つの連続した変換を実行します。どちらも恒等変換であるため、${FOO}を書くのはお洒落な方法です。

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

eval "value=\${$FOO}"

{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?}\""
}
4
Tom Hale

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

あなたはいつも次のようないくつかのステップでそれを行うことができます:

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
3

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?}\""
}
2
Tom Hale

zsh${(P)foo}の代わりに${$foo}が機能しないのと同様に、${${$foo}}も機能しません。間接参照の各レベルを指定するだけです。

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

もちろん、bashはネストされた置換を許可しないため、bashでは${!${!foo}}は機能しません。

2
chepner