web-dev-qa-db-ja.com

オンライン正規表現エンジンではなく、grepの正規表現でエスケープ文字を引用する必要があるのはなぜですか?

この質問のいくつかのバージョンが以前に尋ねられ、回答されたことは確かですが、私は周りを見渡して、正確な答えを見つけていません。おそらく、ここにいる誰かが私のために電球を続けるのを手伝ってくれるでしょう。私はMojave 10.14.6およびbash 3.2.57(1)リリースのMacを使用しています。

私はオンラインチュートリアルに従って正規表現の基本を学び、オンラインサイト https://regexr.com で練習し、bashでgrepを使用しています私のローカルマシンで。

私は、3つのものを含む小さなテキストファイル(small.txtと呼ばれる)で練習しています。

9.00
9-00
9500

.ワイルドカードがその場所の任意の1文字と一致することを理解しています。したがって、/9.00/gを使用しているオンライン正規表現エンジン(JavaScript)では、9.009-00および9500の3つの文字列すべてに一致します。

コマンドラインでgrepを使用しても同じです。

~/bin $ grep 9.00 small.txt
9.00
9-00
9500

ここまでは順調ですね。チュートリアルでは、.をメタキャラクターからリテラルに変換するには、エスケープする必要があると述べています。はい。そのため、オンライン正規表現ボックスに/9\.00/gを入力しても、期待どおりに9.00にのみ一致し、9-00や9500には一致しません。

ただし、同じ構文をコマンドラインのgrepに入力すると、予期しない結果が得られます。

~/bin $ grep 9\.00 small.txt
9.00
9-00
9500

以前と同じです。 grepを機能させるには、文字列全体を二重引用符で囲む必要があります。

~/bin $ grep "9\.00" small.txt
9.00

または、エスケープ文字を二重引用符で囲みます。

~/bin $ grep 9"\."00 small.txt
9.00

他にも、正しい結果が得られる引用の選択がいくつかあるかもしれません。

これにより、正規表現の基本に頭を悩ますことが難しくなります。なぜなら、最初にシェルのgrepがどのように異なるかを理解する必要があるためです従来の正規表現構文から。正規表現のすべてのルールを学ぶのは十分難しいですが、古典的な正規表現とbashシェルの動作の違いを追加すると、頭が爆発します。

とにかく、これを私に明らかにし、コマンドラインでgrepで使用できる正規表現を適切に学習するための道筋を立てる明確な説明があったかどうか疑問に思います。

(正規表現に関するコースでは、bashを使用したgrepのコマンドラインバージョンと、オンラインの正規表現テスターで表示される「純粋な」正規表現構文との違いを指摘していません。)エンジンの違いは、上級レベルですが、これは非常に基本的なもののようで、何かを見逃しているに違いないと感じています。

ありがとう。

10
dbates

どうして?シェルが例の\などのいくつかの特殊文字を解釈するためです。

シェルを介してgrepに引数として渡そうとする文字列を保護していないため、問題が発生しています。

いくつかのソリューション:

  • 文字列を一重引用符で囲み、
  • 文字列を二重引用符で囲む(シェルは二重引用符で囲まれると、結果の文字列をコマンドに送信する前に、$variablesなどのいくつかのことを解釈します)。
  • または引用符を使用しない(強くお勧めします)が、適切な場所にバックスラッシュを追加して、シェルがコマンドに送信する前に次の文字を解釈しないようにします。

文字列をほぼすべて文字どおりに保つため、文字列を単一引用符で保護することをお勧めします。

grep '9\.0' #send those 4 characters to grep in a single argument

シェルは、単一引用符で囲まれた文字列を文字通り渡します。

注:単一引用符で囲まれたシェル文字列内に含めることができない唯一のものは、単一引用符です(これにより単一引用符が終了します)。単一引用符で囲まれたシェル文字列内に単一引用符を含めるには、最初に単一引用符を終了し、エスケープされた単一引用符\'(または二重引用符の間の1つ:"'")を追加し、すぐに単一引用符を再入力して単一引用符を続行する必要があります。引用符で囲まれた文字列:シェルがコマンドgrep a'bを実行するようにするには、パラメーターを'a'\''b'として書き込み、シェルがa'bをgrepに送信するようにできます:grep 'a'\''b' 、またはgrep 'a'"'"'b'

引用を使用しないと主張する場合は、シェルが\\\をgrepに送信するようにする必要があります。

grep 9\\.0  # ie: a 9, a pair \\, a ., and a 0 , and the Shell interprets the pair \\ into a literal \

二重引用符を使用する場合:シェルがいくつかのことを最初に解釈することを考慮する必要があります($vars\など)。たとえば、エスケープされていない、または引用符で囲まれていない\が検出された場合、次の文字がそれを解釈する方法を決定するのを待ちます。 \wは1文字wと見なされ、\\は1文字\と見なされます。

grep "9\\.0"  # looks here the same as not quoting at all... 
    #but doublequoting allows you to have spaces, etc, inside the string
13
Olivier Dulac

コメントを回答に変える:

問題は、\が正規表現とシェルの両方のエスケープ文字であることです。 \.はシェルにとって'.'と同じです。 echoおよびset -xは、シェルの機能を理解するのに役立ちます。

> echo \.
.

> echo '\.'
\.

> echo \\.
\.


> set -x
> echo 9_00 | grep 9\.00
+ echo 9_00
+ grep 9.00
9_00

したがって、コマンドが\を参照する場合は、引用符または2番目の\で保護する必要があります。

9
Hauke Laging

他の回答とコメントに追加するには、grepを取得して必要なものを返すために実行できるもう1つのことは、以下を使用することです。

grep -F 9.00 small.txt

出力:

9.00

-Fは、パターンを正規表現ではなく固定文字列としてgrepに認識させるため、正確な文字列を含む行のみが返されます。このため、.を文字として認識せず、9.00と完全に一致するため、.をエスケープしたり、引用符を使用したりする必要はありません。

2
Nasir Riley

オンライン正規表現エンジンではなく、grepの正規表現でエスケープ文字を引用する必要があるのはなぜですか?

grepの場合は引用符で囲む必要はありませんが、シェルの場合は引用します。

grep -fを使用してファイルからパターンを読み取ると、表示した9\.00パターンがシェルを通過しなくても問題なく機能することがわかります。

$ cat re.txt 
9\.00
$ grep -f re.txt small.txt 
9.00

問題がgrep自体ではないという事実が、おそらく正規表現に関する記事に表示されない理由です。ただし、シェルのしくみに関する記事で関連するポイントが表示される場合があります...

上級レベルのエンジンには違いがあることを知っています

高度である必要はありません。 +などは、BREとEREで動作が異なります。また、少なくとも一部のオンラインツールはデフォルトでPerl正規表現などに設定されており、標準正規表現にはない多くの機能があります。

見る:

1
ilkkachu