Grep RegExからのグループのキャプチャ


380

sh(Mac OSX 10.6)には、ファイルの配列を調べるためのこの小さなスクリプトがあります。この時点で、Googleは役に立たなくなっています。

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

これまでのところ(明らかに、シェルの教祖にとって)は、ファイル名が提供された問題と一致したことが判明した$nameかどうかに応じて、0、1、または2を保持grepします。私が欲しいのは、括弧の中にあるものをキャプチャ([a-z]+)し、それを変数に格納することです

できれば使用しgrepたいだけです。そうでない場合は、PythonやPerlなど、sedまたはそのようなものは使用しないでください。私はシェルが初めてなので、* nixの純粋主義的な角度から攻撃したいと思います。

また、超クールなボヌスとして、シェルで文字列を連結する方法について知りたいのですが?キャプチャしたグループは$ nameに格納されている文字列 "somename"でしたcat $name '.jpg'か、それの最後に文字列 ".jpg"を追加したいと思いますか?

時間があれば、何が起こっているのか説明してください。


30
grepは本当に sedより純粋なUNIXですか?
マーティンクレイトン

3
ああ、それを示唆するつもりはなかった。私がここで特に学習しようとしているツールを使用して解決策が見つかることを期待していました。を使用して解くことができない場合はgrep、を使用して解くsedことができればすばらしいでしょうsed
アイザック

2
私はその上に:)を置くべきだった...
マーティンクレイトン

Psh、私の脳は今日は揚げすぎです。
アイザック

2
@martinclaytonそれは興味深い議論になるでしょう。grepはその名前をed式g(lobal)/ re(gular expression)/ p(rint)から派生しているため、sed(または正確にはed)の方が古い(したがって、より純粋な?多分?)unixだと本当に思います。
誕生した

回答:


500

Bashを使用している場合は、使用する必要もありませんgrep

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

正規表現を変数に入れることをお勧めします。一部のパターンは、文字どおりに含まれていると機能しません。

これ =~は、Bashの正規表現一致演算子を使用します。一致の結果はという配列に保存され$BASH_REMATCHます。最初のキャプチャグループはインデックス1に格納され、2番目のキャプチャグループ(存在する場合)はインデックス2に格納されます。インデックス0は完全一致です。

アンカーがない場合、この正規表現(およびを使用するものgrep)は、以下の例などと一致しますが、探しているものとは異なる場合があることに注意してください。

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

2番目と4番目の例を削除するには、次のように正規表現を作成します。

^[0-9]+_([a-z]+)_[0-9a-z]*

これは、文字列が1つ以上の数字で始まる必要があることを示しています。カラットは文字列の始まりを表します。次のように正規表現の最後にドル記号を追加すると、

^[0-9]+_([a-z]+)_[0-9a-z]*$

次に、ドットが正規表現の文字の中になく、ドル記号が文字列の終わりを表すため、3番目の例も削除されます。4番目の例もこの一致に失敗することに注意してください。

あなたがGNUを持っているならgrep(約2.5以降、\K演算子が追加されたときだと思います):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

\Kオペレータ(可変長ルックビハインド)は直前のパターンが一致させるが、結果に一致するものを含んでいません。同等の固定長は(?<=)-パターンは右括弧の前に含まれます。あなたは使用する必要があります\K数量が異なる長さの文字列を一致させる可能性がある場合(例えば+*{2,4})。

(?=)オペレータは、固定または可変長のパターンと「先読み」と呼ばれていると一致します。また、一致した文字列は結果に含まれません。

大文字と小文字を区別せずに一致させるには、(?i)演算子を使用します。それはそれに続くパターンに影響を与えるので、その位置は重要です。

ファイル名に他の文字があるかどうかに応じて、正規表現を調整する必要がある場合があります。この例では、部分文字列がキャプチャされると同時に文字列を連結する例を示しています。


48
この回答では、「正規表現を変数に入れる方がよい。文字どおりに含めた場合、一部のパターンが機能しない」という特定の行に賛成票を投じたいと思います。
ブランディン2014年

5
@FrancescoFrassinelli:例として、空白を含むパターンがあります。エスケープするのは厄介であり、引用符を使用することはできません。正規表現から通常の文字列に強制するからです。それを行う正しい方法は、変数を使用することです。割り当ての際に引用符を使用すると、作業がはるかに簡単になります。
追って通知があるまで一時停止。

5
/Kオペレーターが揺れる。
ラズ14

2
@ブランドン:機能します。どのバージョンのBashを使用していますか?それがうまくいかない場合、あなたが何をしているのかを見せてください。おそらく、その理由をお話しできます。
追って通知があるまで一時停止。

2
@mdelolmo:私の回答にはに関する情報が含まれていますgrep。それもOPに受け入れられ、かなり賛成しました。反対票をありがとう。
追って通知があるまで一時停止。

145

これはgrep、少なくとも一般的には、pure では実際には不可能です。

ただし、パターンが適切な場合はgrep、パイプライン内で複数回使用して、最初に行を既知の形式に減らし、次に必要なビットだけを抽出することができます。(ツールが好きなもののcutおよびsedこれをはるかに優れています)。

議論のために、パターンが少し単純であると仮定します。[0-9]+_([a-z]+)_次のように抽出できます。

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

1つ目grepはパターン全体に一致しない行を削除し、2つ目grep--only-matching指定したもの)は名前のアルファ部分を表示します。これは、パターンが適切であるためにのみ機能します。「アルファ部分」は、必要なものを引き出すのに十分具体的です。

(余談:個人的に私はgrep+ cutを使用してあなたが何をしているのかを達成します:echo $name | grep {pattern} | cut -d _ -f 2。これはcut区切り文字_で分割することによって行をフィールドに解析し、フィールド2のみを返します(フィールド番号は1から始まります))。

Unixの哲学は、1つのことをうまく行うツールを用意し、それらを組み合わせて重要なタスクを達成することです。そのため、grep+ sedなどは、よりUnixyな方法であると主張します:-)


3
for f in $files; do name=エコー$ f | grep -oEi '[0-9] + _([az] +)_ [0-9a-z] *' | カット-d _ -f 2 ;あはは!
アイザック

2
私はその「哲学」に同意しません。外部コマンドを呼び出さずにシェルの組み込み機能を使用できる場合は、スクリプトのパフォーマンスが大幅に向上します。機能が重複するいくつかのツールがあります。例:grep、sed、awk。これらはすべて文字列操作を行いますが、awkはさらに多くのことができるため、awkはそれらすべての上で際立っています。実際には、上記のダブルgrepsやgrep + sedなどのコマンドのチェーンはすべて、1つのawkプロセスで実行することで短縮できます。
ghostdog74

7
@ ghostdog74:多くの小さな操作を一緒にチェーンすることは、一般にすべてを1か所で行うよりも効率が悪いという議論はありませんが、私はUnixの哲学は多くのツールが一緒に機能しているという主張を支持します。たとえば、tarはファイルをアーカイブするだけで、ファイルを圧縮しません。デフォルトでSTDOUTに出力するため、netcatを使用してネットワーク全体にパイプするか、bzip2などを使用して圧縮できます。 Unixツールがパイプで一緒に動作できるはずであるという信念。
RobM 2009

カットは素晴らしいです-ヒントをありがとう!ツールと効率性の議論については、ツールの連鎖の単純さが好きです。
ether_joe 2014年

grepのoオプションの小道具、それは非常に役立ちます
chiliNUT

96

これについてはすでに回答が受け入れられていると思いますが、「厳密には* nix純粋主義者の立場から」、この仕事に最適なツールであるとpcregrep思われますが、まだ言及されていないようです。行を変更してみてください:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

次へ:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

キャプチャグループのコンテンツのみを取得するには1。

このpcregrepツールは、で既に使用したものと同じ構文をすべて利用しますが、grep必要な機能を実装します。

このパラメータ-oは、grepバージョンが裸の場合と同じように機能しますがpcregrep、表示するキャプチャグループを示すの数値パラメータも受け入れます。

このソリューションでは、スクリプトに必要な最小限の変更があります。単に1つのモジュラーユーティリティを別のユーティリティに置き換え、パラメーターを調整するだけです。

興味深い注:複数の-o引数を使用して、複数のキャプチャグループを行に表示される順序で返すことができます。


3
pcregrepMac OS XOPが使用するデフォルトでは使用できません
grebneke

4
私はpcregrep後の数字を理解していないようです-o: 『-O1「不明なオプション文字『1』』またそのfunctionaliyの言及が見ていないとき。pcregrep --help
ピーターHerdenborg

1
@WAF申し訳ありませんが、コメントにその情報を含めるべきだったと思います。私はCentos 6.5を使用しており、pcregrepのバージョンは明らかに非常に古いです7.8 2008-09-05
Peter Herdenborg、2015

2
ええ、とても役に立ちます。例echo 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei 2016年

5
pcregrep8.41(apt-get install pcregreponでインストールUbuntu 16.03)が-Eiスイッチを認識しません。それがなくても完璧に動作します。macOSでは、@ anishpatelで前述したように(8.41でも)pcregrepインストールされhomebrewているため、少なくともHigh Sierraでは-Eスイッチも認識されません。
Ville

27

信じられないgrepでは不可能

sedの場合:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

私はボーナスで刺します:

echo "$name.jpg"

2
残念ながら、そのsedソリューションは機能しません。それは単に私のディレクトリ内のすべてを出力します。
アイザック

更新され、一致がない場合は空白行が出力されるので、必ず確認してください
cobbal

空白行のみが出力されます!
アイザック

このsedには問題があります。括弧をキャプチャする最初のグループはすべてを網羅しています。もちろん、\ 2には何もありません。
ghostdog74

いくつかの簡単なテストケースで機能しました... \ 2は内部グループを取得します
cobbal '12

16

これはgawkを使用するソリューションです。頻繁に使用する必要があるので、関数を作成しました

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

使うだけ

$ echo 'hello world' | regex1 'hello\s(.*)'
world

素晴らしいアイデアですが、正規表現のスペースでは機能しないようです。スペースで置き換える必要があります\s。あなたはそれを修正する方法を知っていますか?
Adam Ryczkowski、

4

あなたへの提案-パラメータ展開を使用して、最後のアンダースコア以降から名前の一部を削除することができ、同様に最初に:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

次にname、値がありますabc

Apple 開発者向けドキュメントを参照し、「パラメータ拡張」を前方検索してください。


これは([az] +)をチェックしません。
ghostdog74

@levislevis-それは本当ですが、OPによるコメントのとおり、必要なことを行います。
マーティンクレイトン

2

bashがある場合は、拡張グロビングを使用できます

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

または

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

面白そうですね。少し説明をつけてもらえますか?または、その傾向がある場合は、それを説明する特に洞察力に富んだリソースにリンクしますか?ありがとう!
アイザック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.