なぜ感嘆符「!」がbashを混乱させるのですか?


13

!コマンドライン履歴のコンテキストでコマンドラインに特別な意味があることを認識していますが、それ以外に、実行スクリプトでは感嘆符が解析エラーを引き起こすことがあります。
私はそれが何かに関係していると思いますeventが、私はイベントが何であるか、それが何をするのか分かりません。それでも、同じコマンドは異なる状況で異なる動作をする可能性があります。
以下の最後の例では、エラーが発生します。しかし、なぜ、同じコードがコマンド置換の外側で機能したのですか?.. GNU bash 4.1.5を使用する

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found


@Warren ..ありがとう。私はQAことを見ていたが、それは本当に唯一の私の問題は、多くの理由に関係する...バックスラッシュをエスケープする方法について語っ一見 ...すでにエスケープコードが1つの状況で動作していない別
Peter.O

@fred:「すでにエスケープされているように見える」?エスケープがまったく表示されず、二重引用符を使用しています。私の(改訂された)答えを見てください。どの部分がエスケープされていると思いますか?
カレブ

@カレブ。はい、間違った用語を使用しましたprotected。(「単一引用符」で保護されています)
-Peter.O

単純な割り当てにのみ関心がある場合は、var=$(…)(二重引用符なしで)使用できます。これは期待どおり(と思う)に動作します。(これは、例えば(組み込みコマンドを介して行わ割り当ての真実ではないかもしれないが、単純な代入の値の一部が単語分割の対象ではありませんかグロブので、これはまだ「安全」でexportlocalすべてのシェルの下で)、など)。残念ながら、これは単純な割り当てを超えて拡張されません。二重引用符は、他のコンテキストで他のタイプの拡張を取得しながら、単語の分割やグロビングから保護する方法だからです。
クリスジョンセン

回答:


11

!キャラクターは、bashの歴史置換を呼び出します。文字列が続くと(失敗した例のように)、その文字列で始まった最後の履歴イベントまで展開しようとします。$varその文字列の値に展開されるよう!echoに、履歴の最後のechoコマンドに展開されます。

このような拡張では、スペースは破壊的な特性です。最初に、これが変数でどのように機能するかに注意してください。

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

同じことは履歴の拡張でも起こります。強打文字(!)は履歴置換シーケンスから始まりますが、その後に文字列が続く場合のみです。スペースに続いて、置換シーケンスの一部ではなく、リテラルbangにします。

単一引用符を使用すると、変数と履歴の両方の拡張のこの種の置換を回避できます。最初の例では単一引用符を使用したため、問題なく実行されました。最後の例は二重引用符で囲まれているため、bashは他の処理を行う前に拡張シーケンスをスキャンしました。最初のものがトリップしなかった唯一の理由は、上記のようにスペースがブレーク文字であるということです。


カレブに感謝します。私の先入観のもう1つは却下されました... bashの解析は最も内側のブラケットまたはブレースから行われたと考え、その後外側に向かって動作しました... bashの解析は私の想定とは異なるようです。
Peter.O

1
引用符は、ネストされた文字列で変更することなく、bashで十分に混乱します。現状では、交換はプロセスの非常に早い段階で行われます。この例を考えてみましょうvar=word; echo "test '$var'"; echo 'test "$var"'
。–カレブ

..はい。引用符内に引用符をネストすることを知っていました...私の誤解は、コマンド置換の括弧内のコードは、それらの括弧を囲むものに別々に解析されると思っていたことでした。しかしどうやら..ありがとう。
Peter.O

6

Calebで既に述べたように!bashの履歴置換を呼び出すために使用されます。

私のように、あなたがそのような機能を必要としないと感じたら、次の行を挿入して無効にすることができます~/.bashrc

set +H

歴史がアップ矢印とによって回収することができるので、私はそれを必要としないCtrl- rインクリメンタル逆探索。ショートカットの詳細なリストについては、bashのマニュアルページの「履歴操作するコマンド」セクションを参照してください。


2
なしでどうやって生き!!ますか?
カレブ

おかげで、それは移植性の点で問題になると思いますがset +H、スクリプトでの使用も同様に機能します:) +1
Peter.O

2
@fred:奇妙な、一般に履歴展開は対話型シェルに対してのみ「オン」です。
-enzotib

@enzo ..再びありがとう..コマンドラインからテストしました。学習がそれほど面白くないなら、それは退屈でしょう...コーヒーについて言及しましたか?それも役立ちます:)
Peter.O

ええ、それは落とし穴です。この理由でコマンドラインで失敗したスクリプトから貼り付けたコードをコピーしました。履歴の拡張はスクリプトでは問題になりませんでしたが、対話型シェル上にあります。
カレブ

2

あなたの最初の例:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

に減らすことができます

echo '! p' 
echo '!p'

単一引用符内では、すべての文字はリテラル値を保持します。したがって!、特別な意味を失い、歴史の拡張は行われません。

2番目と3番目の例:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

に減らすことができます

echo "'! p'"

echo "'!p'"

'! p''!p'は、本質的に二重引用符で囲まれた文字列の一部です。

二重引用符内では、すべての文字がそのリテラル値を保存する以外 $`\!

これは、単一引用符を意味'! p'し、'!p'その特別な意味を失っている(すなわち:脱出することができませんでし!)が、!まだこれ履歴展開が行われ、その特別な意味を保持します。

ただし、!後にスペース文字が続く場合、履歴展開は実行されません。

からの引用man bash

見積り

[...]

文字を一重引用符で囲むと、引用符内の各文字のリテラル値が保持されます。[...]

文字を二重引用符で囲むと、引用符内のすべての文字のリテラル値が保持されますが、$、 `、\、および履歴展開が有効になっている場合は![...]有効にした場合、!二重引用符で囲むと、バックスラッシュを使用してエスケープされます。!の前のバックスラッシュ 削除されません。

歴史的拡大

[...]

履歴展開は、履歴展開文字の出現によって導入されます。デフォルトで。履歴展開文字を引用できるのは、バックスラッシュ(\)と一重引用符のみです。

スペース、タブ、改行、キャリッジリターン、および=のように、引用符で囲まれていない場合でも、履歴展開文字の直後に見つかった場合、いくつかの文字が履歴展開を禁止します。extglobシェルオプションが有効になっている場合、(も展開を禁止します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.