bash: 変数置換における末尾のスペースのキャプチャ

bash: 変数置換における末尾のスペースのキャプチャ

BASH 4.3.48 (SLES12 SP4) および BASH 4.4.23 (OpenSUSE Leap 15.1) で変数の値から複数の末尾のスペースを削除しようとすると、次のエラーが発生します。

~> xxx="-O -Wall  "
~> echo "X${xxx%% }X"    # (1)
X-O -Wall X
~> echo "X${xxx%% *}X"
X-OX
~> echo "X${xxx% }X"
X-O -Wall X
~> echo "X${xxx% *}X"    # (2)
X-O -Wall X
~> echo "X${xxx%% \*}X"
X-O -Wall  X

(1)またはのどちらかで(2)十分だと思います。

マニュアルには次のように記載されています${parameter%%word}:

一致するサフィックス パターンを削除します。単語は、パス名展開の場合と同様にパターンを生成するために展開されます。パターンがパラメーターの展開された値の末尾の部分に一致する場合、展開の結果は、最短の一致パターン (「%」の場合) または最長の一致パターン (「%%」の場合) が削除されたパラメーターの展開された値になります。

-Wallドキュメントどおりに動作しないので (または私がドキュメントを理解しているとおりに動作しないので)、これは BASH のバグ ( " " の場合に一致しないサフィックス (" ") が削除される%% *) ではないかと考えています。私の考えは正しいでしょうか?

答え1

ではecho "X${xxx%% }X"、パターンは 1 つのスペースです: 。その最長の一致部分は、まさに 1 つのスペースです。最短の一致部分も、まさに 1 つのスペースです。

それ以上のことをするには、グロブ演算子 が必要です*。しかし、これは を除けば何にでもマッチします-Wall。Bash のグロブは正規表現 と同等のものを直接サポートしていませんa*拡張グロビング:

$ shopt -s extglob
$ echo "X${xxx%%+( )}X"
X-O -WallX

答え2

接尾辞の削除内で接頭辞の削除を使用します。

$ xxx="-O -Wall  "
$ echo "X${xxx%"${xxx##*[! ]}"}X"
X-O -WallX
  • 最後のスペース以外の文字まですべて削除し、末尾のスペースのみを残します。
  • これらのスペースを接尾辞削除のパターンとして使用します
  • 内部パラメータの展開は、パターンとして解釈されないように引用符で囲む必要があります (上記では必要ありませんが、他の場合には役立つ場合があります)。
$ bash -c 'xxx="-O -Wall*   "; echo "X${xxx%%"${xxx##*[! *]}"}X"'
X-O -WallX
$ bash -c 'xxx="-O -Wall*   "; echo "X${xxx%%${xxx##*[! *]}}X"'
XX

不自然な例ですが、内側の展開が引用符で囲まれていない場合、そこに含まれるアスタリスクは外側の展開によってシェル パターンとして扱われます。引用符で囲まれている場合は、リテラルのアスタリスクになります。


観察された動作はバグではなく、単純なシェル パターンの動作方法にすぎません。

${xxx%% }
  • 1つのスペースは1つのスペースです
  • 単一のスペースの最長出現は単一のスペースである
${xxx%% *}
  • 単一のスペースの後に何か/何も続かない最長の出現
  • 何も含まれません-Wall
${xxx% }
  • 単一のスペースの最短出現は単一のスペースです
${xxx% *}
  • 1つのスペースの後に何か/何も続かない最短の出現は1つのスペースです
${xxx%% \*}
  • \*バックスラッシュでエスケープされたアスタリスクであり、リテラルのアスタリスクとして解釈されます。
  • 変数にスペースとアスタリスクが続かない場合、接尾辞は削除されません。

答え3

readこれも機能する可能性があります(IFS「スペース」が含まれていると仮定します):

xxx="-O -Wall  "
read -r xxx <<EOF
$xxx
EOF
echo "X${xxx}X"

出力:

X-O -WallX

  • read入力を次のフィールドに分割しますIFS
  • IFSデフォルトではスペース/タブ/改行なので、先頭と末尾のスペースは削除されます。
  • 変数の最初の行で動作します(複数行の変数には適さない可能性があります。bashを使用できますread -d ''

答え4

単純なパラメータ展開では、一致して削除できるパターンがかなり限られています。文字列の末尾から複数の(繰り返しの)文字を削除するには、まず最初に、一致しないすべての文字を削除します。ない問題の文字${xxx##*[! ]}(末尾のスペースすべて)を削除します。次に、2 番目の手順として、その拡張の結果のすべて(末尾のスペースすべて)を末尾から削除すると、必要な結果(末尾のスペースの削除)が得られます。

$ xxx="-O -Wall  "
$ echo "<${xxx%"${xxx##*[! ]}"}>"
<-O -Wall>

代わりに、bash では拡張グロブを使用できます。

$ shopt -s extglob
$ echo "<${xxx%%+( )}>"
<-O -Wall>

または、より高レベルの代替手段として、正規表現を使用して必要なものを一致させることもできます。

$ regex='(.*[^ ]) +$';
$ [[ $xxx =~ $regex ]] && echo "<${BASH_REMATCH[1]}>" || echo "<$xxx>"
<-O -Wall>

または、スクリプトとして:

#!/bin/bash

xxx=${1:-"-O -Wall  "}

regex='(.*[^ ]) +$'

if    [[ $xxx =~ $regex ]]          # if there are trailing spaces
then 
      echo "<${BASH_REMATCH[1]}>"   # Print the string without spaces
else
      echo "<$xxx>"                 # if there are no trailing spaces.
fi

関連情報