web-dev-qa-db-ja.com

Grep RegExからのグループの取得

私はファイルの配列を調べるために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は、grepがファイル名が提供されたものと一致することを発見したかどうかに応じて、0、1または2を保持するだけです。 私たちが欲しいのは、([a-z]+)という親の中にあるものをキャプチャし、それを変数に格納することです

可能であればgrepのみを使用したい。そうでない場合は、PythonやPerlなどを使用しないでください。sedまたはそれに類するもの - 私はShellが初めてなので、* nixの純粋主義者の立場からこれを攻撃したいと思います。

また、超クールなボーナスとして、シェルで文字列をどのように連結できるかについて興味がありますか?私がキャプチャしたグループは$ nameに格納された "somename"という文字列でしたか?そして私はそれの最後に ".jpg"という文字列を追加したいのですが、cat $name '.jpg'

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

330
Isaac

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)演算子を使用します。それはそれに続くパターンに影響を与えるので、その位置は重要です。

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

444

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

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

議論のために、パターンが少し単純だったとします:[0-9]+_([a-z]+)_これを次のように抽出できます:

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

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

(脇:個人的にgrep + cutを使用して、次のようになります:echo $name | grep {pattern} | cut -d _ -f 2。これはcutを取得し、区切り文字で分割して行をフィールドに解析します_、フィールド2のみを返します(フィールド番号は1から始まります)。

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

132
RobM

答えはすでに受け入れられていることを私は理解していますが、「厳密に* 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の数値パラメータも受け入れます。

このソリューションでは、スクリプトに最小限の変更しか必要ありません。あなたは単に一つのモジュール式ユーティリティを他のものと取り替えそしてパラメータを微調整するだけです。

おもしろい注:複数の-o引数を使用して、複数のキャプチャー・グループを行に現れる順序で戻すことができます。

84
John Sherwood

私は信じているgrepだけでは不可能

sedの場合

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

私はボーナスを突き刺すつもりだ:

echo "$name.jpg"
25
cobbal

これはgawkを使った解決策です。それは私が私が頻繁に使用する必要があると思うので私はそれのための関数を作成しました

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

使用するだけ

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

あなたへの提案 - 最後のアンダースコア以降の名前の部分を削除するためにパラメータ展開を使うことができます。

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

nameabcという値になります。

Apple の開発者向けドキュメント を参照して、 'Parameter Expansion'を検索してください。

4
martin clayton

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
2
ghostdog74