sed での非貪欲マッチング

sed での非貪欲マッチング

bash スクリプトには次の変数があります。

file_name='this_is_the_hart_part.csv'

使用

var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')

変数 $file_name のアンダースコア 2 と 3 の間に含まれる部分文字列「the」を抽出します。

しかし、$var2 は $file_name と等しくなります。sed コマンドをどのように変更すればよいでしょうか?

答え1

でサポートされている正規表現の種類では、sedとの非貪欲な一致は許可されません*

3 番目の で区切られたフィールドを取得したい場合_、 を使用すると最も簡単に実行できますcut

cut -d '_' -f 3

または、次のようにしますawk:

awk -F '_' '{ print $3 }'

または、シェルでは、最初の 2 つのフィールドを連続して削除し、末尾をトリミングします。

str=${file_name#*_}
str=${str#*_}
str=${str%%_*}

"$str"最後の単語になりますthe。この最後のバリエーションを使用するのが、これら 3 つの方法の中で最も速く、最も堅牢な方法でしょう。

変数置換により、先頭ビットから最初のアンダースコアまでが削除された${variable#*_}文字列が生成されます。 は、最初のアンダースコアから の末尾までのすべてを削除します。これらは標準的な変数置換です。$variable${variable%%_*}$variable

ファイル名に変数置換を使用する利点は、 や では処理できawkない、改行を含むファイル名に対処できることです。一般に、ファイル名には行指向のテキスト編集ツールを使用しないでください。sedcut

さらに、 を使用していますecho $file_name$file_nameは引用符で囲まれていないため、 の一部であるすべての文字 ($IFSデフォルトではスペース、タブ、改行) に対して単語分割が行われ、生成された単語にファイル名グロブ文字が含まれている場合、シェルによって現在のディレクトリ内のファイル名と照合されます。 また、ファイル名のバックスラッシュも消えたり、望ましくない効果が生じることがあります (展開を引用符で囲んだ場合でも)。 が引用符で囲まれていない場合ksh、シェルは の値に対して中括弧展開も行います$file_name

答え2

まず最初に注意すsedべきことは文章デフォルトでは1行ずつ処理するユーティリティですが、ファイル名には任意の文字(改行を含む)や非文字(非文字でもよい)を含めることができます。文章)。

また、変数を引用符で囲まないことは非常に特別な意味を持つ、そんなことはまずしたくないでしょう、それはまた潜在的に非常に危険

また、echo任意のデータを出力するためには使用できないので、printf代わりに

また、Bourne のようなシェルでの変数割り当て構文は でありvar=value、 ではありません$var=value

の出力全体echo(または、より正確にはprintf) をsedのパターン スペースに読み込むには、次のようにします。

printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'

次に、 2 番目と 3 番目の間の部分を抽出するコードを追加します_

var2=$(
  printf '%s\n' "$filename" |
   sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)

非貪欲な部分は、 (非文字[^_]*のシーケンス)を使用して対処されますが、保証に反して、境界を超えた一致は発生しません(ただし、多くの実装では、非文字で依然として問題が発生します)。_.*_

この場合、代わりにシェル パラメータ拡張演算子を使用できます。

case $filename in
  (*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
  (*)       var2=;;
esac

ファイル名がテキストでない場合、または抽出する部分が改行文字で終わる場合、これはより適切に機能します (また、より効率的です)。

いくつかのシェルには、より高度な演算子が備わっているzshか、または次のようなものがあります:ksh93

  • zsh:

    分割して_3番目のフィールドを取得します:

    var2=${"${(@s:_:)filename}"[3]}
    

    および後方参照を使用します${var/pattern/replacement}(その場合、まず変数に少なくとも 3 つのアンダースコアが含まれていることを確認する必要があります。含まれていない場合は、置換は行われません)。

    set -o extendedglob
    var2=${filename/(#b)*_*_(*)_*/$match[1]}
    
  • ksh93:

    var2=${filename/*_*_@(*)_*/\1}
    

答え3

@Kusalananda さんのおっしゃる通り、これはsed間違ったツールであり、非貪欲なマッチングはできません。しかし、非貪欲なマッチングの回避策として、以下 [^_]*のものを使用できます。_

したがって、あなたの場合は次のようなことができます:

printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'

しかし...あなたのユースケースでは、他のツールを使用する方が良いでしょう...

関連情報