sedでの貪欲でない(消極的な)正規表現一致?


406

sedを使用してURLの行をクリーンアップし、ドメインのみを抽出しようとしています。

だからから:

http://www.suepearson.co.uk/product/174/71/3816/

が欲しいです:

http://www.suepearson.co.uk/

(末尾のスラッシュの有無にかかわらず、それは問題ではありません)

私が試してみました:

 sed 's|\(http:\/\/.*?\/\).*|\1|'

および(貪欲でない数量詞をエスケープする)

sed 's|\(http:\/\/.*\?\/\).*|\1|'

しかし、貪欲でない量指定子(?)が機能しないように見えるため、常に文字列全体と一致します。


54
補足:正規表現を「|」で区切る場合、「/」をエスケープする必要はありません。実際、ほとんどの人は "|"で区切ります 「ポケットフェンス」を回避するために「/」の代わりに。
AttishOculus 2009年

12
@AttishOculus sedの置換式の「s」の後の最初の文字は区切り文字です。したがって、「s ^ foo ^ bar ^」または「s!foo!bar!」仕事も
イカ

1
拡張正規表現の場合は、を使用します sed -E 's...。それでも、消極的なオペレーターはいません。
OndraŽižka18年

質問のタイトルには答えませんが、この特定のケースでは単純なcut -d'/' -f1-3作品です。
Petr Javorik

回答:


421

基本的なPosix / GNU正規表現も、貪欲でない量指定子も認識しません。後で正規表現が必要です。幸いなことに、このコンテキストのPerl正規表現は簡単に入手できます。

perl -pe 's|(http://.*?/).*|\1|'

12
これを適切に行うには、オプションを使用します-pi -e
13

11
聖は、私が吸う働いていた:-)唯一のことは、今私のスクリプトはPerlの依存関係を持っていることを:-(プラス側では、事実上すべてのLinuxディストリビューションはそうおそらくない問題:-)すでにPerlを持っていると信じてすることはできません喫煙
Freedom_Ben

6
@Freedom_Ben:IIRC perlはPOSIX で必要
MestreLion 2015

4
@ dolphus333:「基本的なものでも拡張されたPosix / GNU正規表現でも貪欲でない数量詞は認識されません」とは、「sedで貪欲でない数量詞を使用できない」という意味です。
混沌

3
@Sérgioこれは、要求された処理を行う方法です。これはsed、基本的に同じ構文を使用してで不可能ですsed
chaos

251

この特定のケースでは、貪欲でない正規表現を使用せずに仕事を完了することができます。

次の[^/]*代わりに貪欲でない正規表現を試してください.*?

sed 's|\(http://[^/]*/\).*|\1|g'

3
このテクニックを使用して、sed一致を貪欲でないフレーズに一致させる方法は?
user3694243

6
残念ながらできません。カオスの答えを見てください。
ダニエルH

多くの感謝... perlは多くのLinuxディストリビューションのデフォルトのインストールベースではなくなったので!
st0ne 2017


@DanielH実際には、要求に応じてこの手法使用して、欲張らずにフレーズを一致させることができます。どちらのパターンも十分な精度で書くのは少し面倒かもしれません。たとえば、URLのクエリでKey-Value-Assignmentを解析する場合、を使用してArchの割り当てを確認する必要がある場合があります([^&=#]+)=([^&#]*)。この方法で確実に機能しないケースがあります。たとえば、ホストパーツのURLを解析し、最後のスラッシュがオプションであると想定してパス名をキャプチャから除外すると仮定した場合:^(http:\/\/.+?)/?$
Thomas Urban

121

sedでは、通常、セパレーターまでのセパレーター以外のものを検索して、貪欲でない検索を実装します。

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*\)/.*;\1;p'

出力:

http://www.suon.co.uk

これは:

  • 出力しない -n
  • 検索、パターン一致、置換、印刷 s/<pattern>/<replace>/p
  • ;入力/しやすくするために、代わりに検索コマンドのセパレータを使用しますs;<pattern>;<replace>;p
  • 大括弧間の一致を覚えておいてください\(... \)、後で\1\2...でアクセス可能
  • 一致 http://
  • かっこ内[]に何かが続く場合[ab/]は、aまたはbまたはを意味します/
  • 最初^[]意味notがあるので、その後に[]
  • これ[^/]以外のものを意味し/、文字を
  • *前のグループを繰り返すため、[^/]*以外の文字を意味します/
  • これまでのところsed -n 's;\(http://[^/]*\)、検索して覚え、http://その後に他の文字を除い/て覚えていることを意味します。
  • ドメインの終わりまで検索したいので、次で停止する/ので/、最後に別sed -n 's;\(http://[^/]*\)/'の行を追加します。.*
  • グループ1(\1)に記憶されている一致はドメインなので、一致した行をグループに保存されているものに置き換えて\1印刷します。sed -n 's;\(http://[^/]*\)/.*;\1;p'

ドメインの後にバックスラッシュも含める場合は、グループにバックスラッシュをもう1つ追加して覚えておきます。

echo "http://www.suon.co.uk/product/1/7/3/" | sed -n 's;\(http://[^/]*/\).*;\1;p'

出力:

http://www.suon.co.uk/

8
最近の編集について:括弧は一種の括弧文字なので、特に作者のように単語の後に実際の文字が続く場合は、括弧を括弧と呼ぶことは誤りではありません。また、一部のカルチャーでは推奨される使用法であるため、独自のカルチャーで推奨される使用法に置き換えるのは少し失礼に思えますが、それはエディターが意図したものではないと確信しています。個人的には、丸かっこ角かっこ山かっこのような純粋にわかりやすい名前を使用するのが最善だと思います
アランムーア

2
セパレータを文字列に置き換えることはできますか?
Calculemus 2014年

37

sedは「貪欲でない」演算子をサポートしていません。

「/」を一致から除外するには、「[]」演算子を使用する必要があります。

sed 's,\(http://[^/]*\)/.*,\1,'

PS「/」をバックスラッシュする必要はありません。


あんまり。区切り文字が多くの可能な文字の1つである場合(たとえば、数字の文字列のみ)、否定の一致はますます複雑になる可能性があります。それは問題ありませんが、。*を貪欲にしないオプションがあるといいでしょう
gesell

1
質問はより一般的でした。これらのソリューションは、URLに対しては機能しますが、(たとえば)末尾のゼロを削除する私のユースケースに対しては機能しません。s/([[:digit:]]\.[[1-9]]*)0*/\1/は明らかにうまく機能しません1.20300。ただし、元の質問はURLに関するものだったので、受け入れられた回答にそれらを記載する必要があります。
ダニエルH

33

での遅延(貪欲でない)数量詞のシミュレーション sed

そして、他のすべての正規表現フレーバー!

  1. 式の最初の出現を見つける:

    • POSIX ERE-rオプションを使用)

      正規表現:

      (EXPRESSION).*|.

      セッド:

      sed -r 's/(EXPRESSION).*|./\1/g' # Global `g` modifier should be on

      例(最初の数字のシーケンスを見つける)ライブデモ

      $ sed -r 's/([0-9]+).*|./\1/g' <<< 'foo 12 bar 34'
      12

      それはどのように機能しますか?

      この正規表現は、交替の恩恵を受け|ます。各位置で、エンジンは最長の一致を選択しようとします(これは、他のいくつかのエンジンが後に続くPOSIX標準です)。つまり.、の一致が見つかるまで続き([0-9]+).*ます。しかし、順序も重要です。

      ここに画像の説明を入力してください

      グローバルフラグが設定されているため、エンジンは入力文字列の最後またはターゲットまで、文字ごとにマッチングを続行しようとします。交互の左側の最初で唯一のキャプチャグループが一致(EXPRESSION)するとすぐに、残りの行もすぐに消費されます.*。私たちは今、最初の捕獲グループに価値を持っています。

    • POSIX BRE

      正規表現:

      \(\(\(EXPRESSION\).*\)*.\)*

      セッド:

      sed 's/\(\(\(EXPRESSION\).*\)*.\)*/\3/'

      例(最初の数字のシーケンスを見つける):

      $ sed 's/\(\(\([0-9]\{1,\}\).*\)*.\)*/\3/' <<< 'foo 12 bar 34'
      12

      これはEREバージョンに似ていますが、変更はありません。それで全部です。各単一の位置で、エンジンは数字を照合しようとします。

      ここに画像の説明を入力してください

      それが見つかった場合、他の以下の数字が消費、捕捉及びラインの残りがあるため、すぐにそうでない整合されている*手段 より多くまたはゼロは、第2のキャプチャグループをスキップ\(\([0-9]\{1,\}\).*\)*し、ドットに到達する.単一文字に一致するように、このプロセスが継続します。

  2. 区切られた式の最初の出現を見つける:

    このアプローチは、区切られた文字列の最初の出現に一致します。これを文字列のブロックと呼ぶことができます。

    sed 's/\(END-DELIMITER-EXPRESSION\).*/\1/; \
         s/\(\(START-DELIMITER-EXPRESSION.*\)*.\)*/\1/g'

    入力文字列:

    foobar start block #1 end barfoo start block #2 end

    -EDE: end

    -SDE: start

    $ sed 's/\(end\).*/\1/; s/\(\(start.*\)*.\)*/\1/g'

    出力:

    start block #1 end

    最初の正規表現は\(end\).*最初の終了区切り文字に一致してキャプチャしend、最後の区切り文字である最近キャプチャされた文字とすべての一致を置き換えます。この段階での出力は次のとおりfoobar start block #1 endです。

    ここに画像の説明を入力してください

    次に、結果は\(\(start.*\)*.\)*上記のPOSIX BREバージョンと同じ2番目の正規表現に渡されます。開始区切り文字startが一致しない場合は1文字と一致し、それ以外の場合は開始区切り文字と一致してキャプチャし、残りの文字と一致します。

    ここに画像の説明を入力してください


あなたの質問に直接答える

アプローチ#2(区切り式)を使用して、2つの適切な式を選択する必要があります。

  • EDE: [^:/]\/

  • SDE: http:

使用法:

$ sed 's/\([^:/]\/\).*/\1/g; s/\(\(http:.*\)*.\)*/\1/' <<< 'http://www.suepearson.co.uk/product/174/71/3816/'

出力:

http://www.suepearson.co.uk/

注:これは、同じ区切り文字では機能しません。


3)regex101などのサイトをデモ用に提案している間、構文と機能の違いのため、cliツールには必ずしも適していないというメモを追加してください
Sundeep

1
@Sundeepありがとうございます。私はそれらすべての引用符を単一引用符に変えました。また、左端の最長一致ルールについても言及しました。ただしsed、同等の場合、同じ標準順序に従う他のすべてのエンジンは重要です。したがってecho 'foo 1' | sed -r 's/.|([0-9]+).*/\1/g'、一致はありませんが、一致echo 'foo 1' | sed -r 's/([0-9]+).*|./\1/g'します。
REVO

@Sundeepも、区切り式の回避策が、私がメモを追加した同じ開始および終了区切り文字に対して機能しませんでした。
REVO

別の交替は、同じ場所から開始し、同じ長さを持っている場合、それは他のエンジンのように左から右の順に従ってますね、何が起こるかについての素晴らしい点。..マニュアルに記載されている場合は、ルックアップする必要性
Sundeep

:奇妙な場合は、しかしここにありますstackoverflow.com/questions/59683820/...
Sundeep

20

複数のキャラクターに対する貪欲でない解決策

このスレッドは本当に古いですが、人々はまだそれを必要としていると思います。最初に発生するまですべてを殺したいとしましょうHELLO。あなたは言うことができません[^HELLO]...

したがって、良い解決策は2つのステップを含みますtop_sekrit。たとえば、入力で予期しない一意の単語を節約できると仮定します。

この場合、次のことができます。

s/HELLO/top_sekrit/     #will only replace the very first occurrence
s/.*top_sekrit//        #kill everything till end of the first HELLO

もちろん、より単純な入力で、より小さな単語、あるいは単一の文字を使用することもできます。

HTH!


4
それをさらに良くするには、未使用の文字が期待できない状況で役立ちます。1.その特殊文字を実際に使用されていないWORDで置き換えます。2。終了シーケンスを特殊文字で置き換えます。3。特殊文字で終わる検索を行います。4 。特殊文字を元に戻す、5。特殊文字を元に戻す。たとえば、<hello>と</ hello>の間に貪欲な演算子が必要な場合:
Jakub

3
ここに例:echo "Find:<hello> fir〜st <br> yes </ hello> <hello> sec〜ond </ hello>" | sed -e "s、〜、VERYSPECIAL、g" -e "s、</ hello>、〜、g" -e "s、。* Find:<hello>([^〜] *)。*、\ 1 、 "-e" s、\〜、</ hello>、 "-e" s、VERYSPECIAL、〜、 "
Jakub

2
同意する。素晴らしい解決策。私はコメントを次のように言い換えます:〜未使用に頼ることができない場合は、最初にs /〜/ VERYspeciaL / gを使用して現在の出現箇所を置き換え、次に上記のトリックを実行してから、s / VERYspeciaL /〜/ gを使用して元の〜を返します
ishahak 2014年

1
私はこの種のものにはより珍しい「変数」を使用する傾向があるので、の代わりに`使用します<$$>$$シェルではプロセスIDに展開されるため、単一引用符ではなく二重引用符を使用する必要があります)。あなたの正規表現の他の部分を壊すかもしれません)、またはユニコードが利用可能な場合、のようなもの<∈∋>
Adam Katz

いくつかの時点で、あなたはあなただけ使用していない理由を自問する必要がありperl、またはpython代わりに他のいくつかの言語をか。perlこれを1行でそれほど脆弱ではない方法で実行します...
ArtOfWarfare

18

sed-Christoph Sieghartによる貪欲でないマッチング

sedで貪欲でない一致を取得するコツは、一致を終了させる文字を除くすべての文字を一致させることです。言うまでもありませんが、貴重な時間を無駄にしましたが、結局のところ、シェルスクリプトは迅速かつ簡単なはずです。したがって、他の誰かがそれを必要とする可能性がある場合:

貪欲なマッチング

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

貪欲でないマッチング

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar

17

これはカットを使用して行うことができます:

echo "http://www.suepearson.co.uk/product/174/71/3816/" | cut -d'/' -f1-3

9

正規表現を使用しない別の方法は、fields / delimiterメソッドを使用することです。

string="http://www.suepearson.co.uk/product/174/71/3816/"
echo $string | awk -F"/" '{print $1,$2,$3}' OFS="/"

5

sed 確かにその場所がありますが、これはそれらの1つではありません!

ディーが指摘したように:を使用するだけcutです。この場合、はるかに簡単ではるかに安全です。Bash構文を使用してURLからさまざまなコンポーネントを抽出する例を次に示します。

url="http://www.suepearson.co.uk/product/174/71/3816/"

protocol=$(echo "$url" | cut -d':' -f1)
host=$(echo "$url" | cut -d'/' -f3)
urlhost=$(echo "$url" | cut -d'/' -f1-3)
urlpath=$(echo "$url" | cut -d'/' -f4-)

あなたにあげる:

protocol = "http"
host = "www.suepearson.co.uk"
urlhost = "http://www.suepearson.co.uk"
urlpath = "product/174/71/3816/"

ご覧のとおり、これはより柔軟なアプローチです。

(すべてディーへのクレジット)



3

sed -Eは、正規表現を拡張(最新)正規表現として解釈します

更新:MacOS Xでは-E、GNU sedでは-r。


4
いいえ、そうではありません...少なくともGNU sedではありません。
Michel de Ruiter、2011

7
より広義に-Eは、BSDに固有でsedあり、したがってOS Xに固有です。manページへのリンク。@stephanchegの訂正に記載されているように、GNUに-r拡張正規表現をもたらします。'nixディストリビューション間で既知の変動性のあるコマンドを使用するときは注意してください。私はその難しい方法を学びました。sed
2012年

1
これは、sedを使用する場合の正解であり、最初の質問に最も当てはまります。
2013

8
GNU sedの-rオプションAppendix A Extended regular expressionsは、infoファイルといくつかの簡単なテストに従って、エスケープルールのみを変更します。それは実際に(のような非貪欲修飾子を追加しませんGNU sed version 4.2.1、少なくとも。)
eichin

1
-Eしばらくの間、GNU sed はドキュメント化されていないオプションとして認識されていましたが、リリース4.2.2.177では、それを反映するようにドキュメントが更新されている-Eため、現在はどちらでも問題ありません
ベンジャミンW.17年

3

純粋な(GNU)sedを使用してこれを解決する希望はまだあります。これは一般的な解決策ではありませんが、「ループ」を使用して次のように文字列の不要な部分をすべて削除できる場合もあります。

sed -r -e ":loop" -e 's|(http://.+)/.*|\1|' -e "t loop"
  • -r:拡張正規表現を使用(+およびエスケープされていない括弧の場合)
  • ":loop": "loop"という名前の新しいラベルを定義します
  • -e:sedにコマンドを追加します
  • 「t loop」:置換が成功した場合、ラベル「loop」に戻ります

ここでの唯一の問題は、最後の区切り文字( '/')もカットすることですが、本当に必要な場合は、「ループ」が終了した後で元に戻すことができます。前の最後にこの追加コマンドを追加するだけです。コマンドライン:

-e "s,$,/,"

2

(perl、cutなどの代わりに)sedを使用しようとしていることを具体的に述べたので、グループ化してみてください。これにより、貪欲でない識別子が認識されない可能性があります。最初のグループはプロトコルです(つまり、「http://」、「https://」、「tcp://」など)。2番目のグループはドメインです。

echo "http://www.suon.co.uk/product/1/7/3/" | sed "s | ^ \(。* // \)\([^ /] * \)。* $ | \ 1 \ 2 |"

グループ化に慣れていない場合は、ここから始めてください


1

これは古いエントリだと思いますが、誰かが役に立つと思うかもしれません。完全なドメイン名は全長253文字を超えることはできないため、。*を。\ {1、255 \}に置き換えます。


1

これは、sedを使用して複数文字列の貪欲でないマッチングを確実に行う方法です。あなたは、すべてを変更したいとしましょうfoo...bar<foo...bar>例えばので、この入力を:

$ cat file
ABC foo DEF bar GHI foo KLM bar NOP foo QRS bar TUV

この出力になるはずです:

ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

これを行うには、fooとbarを個々の文字に変換し、それらの間のそれらの文字の否定を使用します。

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/g; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC <foo DEF bar> GHI <foo KLM bar> NOP <foo QRS bar> TUV

上記では:

  1. s/@/@A/g; s/{/@B/g; s/}/@C/g変換される{}、それらの文字は、次に変換するために利用可能であるので、入力に存在することができないプレースホルダストリングへfoobarします。
  2. s/foo/{/g; s/bar/}/gとそれぞれに変換foobarています{}
  3. s/{[^{}]*}/<&>/g希望する操作を実行しています-に変換foo...barしています<foo...bar>
  4. s/}/bar/g; s/{/foo/g変換される{}バックにfooしてbar
  5. s/@C/}/g; s/@B/{/g; s/@A/@/g プレースホルダー文字列を元の文字に変換しています。

上記は、最初のステップでそのような文字列を作成するため、入力に存在しない特定の文字列に依存せず{[^{}]*}、必要に応じて何回でも使用できるため、一致させる特定の正規表現の発生を気にしないことに注意してください必要な実際の一致を分離する式、および/またはseds数値一致演算子を使用して、たとえば2番目の出現のみを置き換える

$ sed 's/@/@A/g; s/{/@B/g; s/}/@C/g; s/foo/{/g; s/bar/}/g; s/{[^{}]*}/<&>/2; s/}/bar/g; s/{/foo/g; s/@C/}/g; s/@B/{/g; s/@A/@/g' file
ABC foo DEF bar GHI <foo KLM bar> NOP foo QRS bar TUV

1

まだこの答えを見たことがないので、viまたはでこれを行う方法は次のvimとおりです:

vi -c '%s/\(http:\/\/.\{-}\/\).*/\1/ge | wq' file &>/dev/null

これにより、vi :%s置換がグローバルに実行され(末尾のg)、パターンが見つからない場合(e)にエラーが発生しなくなり、結果の変更がディスクに保存されて終了します。これ&>/dev/nullにより、GUIが画面上で短時間点滅するのを防ぎます。

I使用してのようにvi(1)perlはあるので、超複雑な正規表現のために、時には(2)Vimがあり、瀕死非常に高度な正規表現エンジンを、そして(3)私はすでにに精通してるvi私の日々の利用編集で正規表現ドキュメント。


0
echo "/home/one/two/three/myfile.txt" | sed 's|\(.*\)/.*|\1|'

気にしないで、私は別のフォーラムでそれを手に入れました:)


4
したがって、貪欲な一致を取得します。、同様に貪欲に一致するような/home/one/two/three/別の/ものを追加する場合:、質問は非貪欲についてです/home/one/two/three/four/myfile.txtfour/home/one/two/three/four
stefanB


0

これは、2ステップのアプローチとawkでできることです。

A=http://www.suepearson.co.uk/product/174/71/3816/  
echo $A|awk '  
{  
  var=gensub(///,"||",3,$0) ;  
  sub(/\|\|.*/,"",var);  
  print var  
}'  

出力:http : //www.suepearson.co.uk

お役に立てば幸いです。


0

別のsedバージョン:

sed 's|/[:alnum:].*||' file.txt

これは、一致した/行の終わりまでの英数字(そうではない別のスラッシュ)だけでなく、文字の残りの部分が続きます。その後、それは何にも置き換えられません(つまり、削除されます)。


1
私はそれがあるべきと思い"[[:alnum:]]"、ありません"[:alphanum:]"
oli_arborum
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.