web-dev-qa-db-ja.com

サブシェルとサブストリングを使用したバッシュの悪い置換

不自然な例...与えられた

FOO="/foo/bar/baz"

これは機能します(bashで)

BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1}       # result is BAZ="b"

これはしません

BAZ=${$(basename $FOO):0:1} # result is bad substitution

私の質問は、この[サブシェル置換]が誤って評価される原因となるルールは何ですか?そして、もしあれば、これを1ホップで行う正しい方法は何ですか?

21
Matt

まず、これを言うときは、次のことに注意してください。

_BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1}       # result is BAZ="b"
_

BAZの構成の最初のビットはBARであり、最初の文字を取得するvalueではありません。したがって、bashで変数名に任意の文字を含めることが許可されている場合でも、2番目の式の結果は希望どおりではありません。

ただし、これを妨げているルールについては、bashのmanページから引用できるようにします。

_DEFINITIONS
       The following definitions are used throughout the rest  of  this  docu‐
       ment.
       blank  A space or tab.
       Word   A  sequence  of  characters  considered  as a single unit by the
              Shell.  Also known as a token.
       name   A Word consisting only of  alphanumeric  characters  and  under‐
              scores,  and beginning with an alphabetic character or an under‐
              score.  Also referred to as an identifier.
_

その後少し後で:

_PARAMETERS
       A parameter is an entity that stores values.  It can be a name, a  num‐
       ber, or one of the special characters listed below under Special Param‐
       eters.  A variable is a parameter denoted by a name.  A variable has  a
       value  and  zero or more attributes.  Attributes are assigned using the
       declare builtin command (see declare below in Shell BUILTIN COMMANDS).
_

そして後でそれがあなたが求めている構文を定義するとき:

_   ${parameter:offset:length}
          Substring Expansion.  Expands to  up  to  length  characters  of
          parameter  starting  at  the  character specified by offset.
_

そのため、マンページに明記されているルールでは、_${foo:x:y}_構文には最初の部分としてパラメーターが必要であり、パラメーターは名前、数字、またはいくつかの特殊パラメーター文字の1つのみにすることができます。 $(basename $FOO)は、パラメーターに許可される可能性の1つではありません。

1つの割り当てでこれを行う方法については、他の応答で言及されているように、他のコマンドへのパイプを使用します。

12
Daniel Martin

${parameter#Word}などの変更された形式のパラメータ置換では、任意のWordではなく、パラメータのみを変更できます。

この場合、次のようにbasenameの出力をddコマンドにパイプすることができます。

BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)

(より多くのカウントが必要な場合は、countではなくbsを増やしてください。そうしないと、要求されたよりも少ないバイトを取得する可能性があります。)

一般的なケースでは、このようなことを1つの割り当てで行う方法はありません。

6
jilles

${BAR:0:1}は変数展開であるため、失敗します。 Bashは、値ではなく${の後に変数名が表示されることを期待しています。

単一の式でそれを行う方法を知りません。

6
Alanyst

他の人が言ったように、$ {}の最初のパラメーターは変数名である必要があります。しかし、別のサブシェルを使用して、実行しようとしていることを概算できます。

の代わりに:

BAZ=${$(basename $FOO):0:1} # result is bad substitution

使用する:

BAZ=$(_TMP=$(basename $FOO); echo ${_TMP:0:1}) # this works
4
bobpaul

考案された例のための考案されたソリューション:

BAZ=$(expr $(basename $FOO) : '\(.\)')

のように

$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j
1
Larry

$ {string:0:1}、stringは変数名でなければなりません

例えば:

FOO = "/ foo/bar/baz"

baz = "foo"

BAZ = eval echo '${'"$(basename $FOO)"':0:1}'

$ BAZをエコー

結果は「f」です

0
user3304852