sedで貪欲でないマッチ[閉じる]

sedで貪欲でないマッチ[閉じる]

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

file_name='this_is_the_hart_part.csv'

使用

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

部分文字列 "the"(変数$ file_nameから2と3の間の下線付きの数字)を抽出したいと思います。

しかし、 $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

ファイル名に変数置換を使用すると、改行文字を含むファイル名を処理できますが、or norawkは処理できないという利点があります。通常、ファイル名には行中心のテキスト編集ツールを使用しないでください。sedcut

また、引用符で囲まれていないecho $file_nameため$file_name、単語分割(デフォルトでは空白、タブ、および改行でもあるすべての文字$IFS)を実行し、結果の単語(ファイル名が一致する文字を含む場合)になります。現在のディレクトリのファイル名とシェルが一致することを確認します。ファイル名のバックスラッシュは消えたり、望ましくない影響を与える可能性があります(拡張子を引用しても)。引用符がない場合、シェルはksh値に対して中括弧拡張も実行します。$file_name

答え2

最初に注意することsedテキストデフォルトでは一度に1行だけ処理するユーティリティですが、ファイル名にはすべての文字(改行を含む)または非文字(文字ではない可能性がある)を含めることができます。テキスト)。

返品、引用符のない変数は非常に特別な意味を持ちます。、あなたはこれをほとんど望んでいません。潜在的に非常に危険

返品、echo任意のデータを出力するために使用することはできません。printf代わりに

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

echoprintf以下を使用して、出力全体を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'

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

関連情報