9

正規表現のネストされた「+」/「-」先読み/後読みに問題があります。

'*'で文字列を変更したいとしましょう。次の文字をエスケープする'%'としましょう。'\'(正規表現をSQLのようなコマンドに変えます^^)。

だから文字列

  • '*test*'に変更する必要があります'%test%'
  • '\\*test\\*'-> '\\%test\\%'、しかし
  • '\*test\*'そして'\\\*test\\\*'変わらないはずです。

私は試した:

(?<!\\)(?=\\\\)*\*      but this doesn't work
(?<!\\)((?=\\\\)*\*)    ...
(?<!\\(?=\\\\)*)\*      ...
(?=(?<!\\)(?=\\\\)*)\*  ...

上記の例の「*」に一致する正しい正規表現は何ですか?

このような視覚的な構造を持つ正規表現の違いと、これらが本質的に間違っている場合の(?<!\\(?=\\\\)*)\*違いは何ですか?(?=(?<!\\)(?=\\\\)*)\*

4

5 に答える 5

11

エスケープされていない文字を見つけるには、偶数(またはゼロ)のエスケープ文字が前に付いている文字を探します。これは比較的簡単です。

(?<=(?<!\\)(?:\\\\)*)\*        # this is explained in Tim Pietzcker' answer

残念ながら、多くの正規表現エンジンは可変長の後読みをサポートしていないため、先読みに置き換える必要があります。

(?=(?<!\\)(?:\\\\)*\*)(\\*)\*  # also look at ridgerunner's improved version

%これをグループ1の内容と記号に置き換えます。

説明

(?=           # start look-ahead
  (?<!\\)     #   a position not preceded by a backslash (via look-behind)
  (?:\\\\)*   #   an even number of backslashes (don't capture them)
  \*          #   a star
)             # end look-ahead. If found,
(             # start group 1
  \\*         #   match any number of backslashes in front of the star
)             # end group 1
\*            # match the star itself

先読みにより、偶数の円記号のみが考慮されるようになります。とにかく、先読みは文字列内の位置を進めないため、それらをグループに一致させる方法はありません。

于 2011-10-23T16:16:09.580 に答える
9

わかりました、ティムは私が提案した改造で彼の正規表現を更新しないことに決めたので(そしてトマラクの答えは合理化されていません)、ここに私の推奨される解決策があります:

置換:((?<!\\)(?:\\\\)*)\*$1%

これは、コメント付きの PHP スニペットの形式です。

// Replace all non-escaped asterisks with "%".
$re = '%             # Match non-escaped asterisks.
    (                # $1: Any/all preceding escaped backslashes.
      (?<!\\\\)      # At a position not preceded by a backslash,
      (?:\\\\\\\\)*  # Match zero or more escaped backslashes.
    )                # End $1: Any preceding escaped backslashes.
    \*               # Unescaped literal asterisk.
    %x';
$text = preg_replace($re, '$1%', $text);

補遺: 非ルックアラウンド JavaScript ソリューション

上記のソリューションは後読みを必要とするため、JavaScript では機能しません。次の JavaScript ソリューションでは、後読みを使用していません。

text = text.replace(/(\\[\S\s])|\*/g,
    function(m0, m1) {
        return m1 ? m1 : '%';
    });

このソリューションでは、バックスラッシュの各インスタンスをそれ自体に置き換え、*アスタリスクの各インスタンスを%パーセント記号に置き換えます。

編集 2011-10-24: Javascript のバージョンを修正して、次のようなケースを正しく処理できるようにしまし**text**た。(以前のバージョンのエラーを指摘してくれた Alan Moore に感謝します。)

于 2011-10-23T16:46:41.253 に答える
5

他の人は、これが後読みでどのように行われるかを示していますが、私は、後読みをまったく使用しないことを主張したいと思います。このソリューションを検討してください(デモはこちら):

s/\G([^*\\]*(?:\\.[^*\\]*)*)\*/$1%/g;

正規表現の大部分は、[^*\\]*(?:\\.[^*\\]*)*Friedlの「展開されたループ」イディオムの例です。アスタリスクやバックスラッシュ以外の個々の文字、またはバックスラッシュの後に何かが続く文字のペアをできるだけ多く消費します。これにより、エスケープされたバックスラッシュ(または他の文字)がいくつ先行していても、エスケープされていないアスタリスクの消費を回避できます。

アンカーは\Gそれぞれ、前の一致が終了した位置に一致するか、これが最初の一致の試行である場合は入力の開始に一致します。これにより、正規表現エンジンがエスケープされたバックスラッシュをスキップして、エスケープされていないアスタリスクと一致するのを防ぎます。したがって、/g制御された一致の各反復は、次のエスケープされていないアスタリスクまでのすべてを消費し、グループ#1のアスタリスクを除くすべてをキャプチャします。次に、プラグを差し込んで、*をに置き換え%ます。

これは、少なくともルックアラウンドが近づくのと同じくらい読みやすく、理解しやすいと思います。のサポートが必要なため\G、JavaScriptやPythonでは機能しませんが、Perlでは問題なく機能します。

于 2011-10-23T23:39:10.850 に答える
4

*つまり、バックスラッシュが偶数個前にある場合 (つまり、エスケープされていない場合) にのみ一致させたいということですか? じゃあ、後ろばかり見てるから先を読む必要ないじゃないですか。

検索する

(?<=(?<!\\)(?:\\\\)*)\*

と置き換え%ます。

説明:

(?<=       # Assert that it's possible to match before the current position...
 (?<!\\)   # (unless there are more backslashes before that)
 (?:\\\\)* # an even number of backslashes
)          # End of lookbehind
\*         # Then match an asterisk
于 2011-10-23T16:01:27.583 に答える
0

正規表現でエスケープされたバックスラッシュを検出する問題は、しばらくの間私を魅了してきましたが、完全に複雑にしすぎていることに最近まで気付きませんでした。簡単にすることがいくつかありますが、私が知る限り、ここにいる誰もまだ気づいていません。

  • バックスラッシュは、他のバックスラッシュだけでなく、その後の任意の文字をエスケープします。その(\\.)*ため、バックスラッシュであるかどうかに関係なく、エスケープされた文字のチェーン全体を食べます。偶数または奇数のスラッシュについて心配する必要はありません。\チェーンの最初または最後にある孤独をチェックするだけです ( ridgerunner のJavaScript ソリューションはこれを利用しています)。

  • チェーンの最初のバックスラッシュから確実に開始する方法は、ルックアラウンドだけではありません。バックスラッシュ以外の文字 (または文字列の先頭) を探すことができます。

その結果、ルックアラウンドやコールバックを必要としない短く単純なパターンが得られ、これまでに見たどのパターンよりも短くなっています。

/(?!<\\)(\\.)*\*/g

そして置換文字列:

"$1%"

これは、後読みが可能な .NETで機能し、Perl でも機能するはずです。JavaScript でそれを行うことは可能ですが、後読みや\Gアンカーがなければ、ワンライナーで行う方法がわかりません。Ridgerunner のコールバックは、ループと同様に機能するはずです。

var regx = /(^|[^\\])(\\.)*\*/g;
while (input.match(regx)) {
    input = input.replace(regx, '$1$2%');
}

ここには、他の正規表現の質問で知っている名前がたくさんあります。私より賢い人もいると思います。私が間違いを犯した場合は、そう言ってください。

于 2012-10-16T20:38:43.317 に答える