最初の発生のみをsedに置き換えたい


26

元のファイル

claudio
antonio
claudio
michele

「claudia」で「claudio」の最初の出現のみを変更したいので、ファイル結果

claudia
antonio
claudio
michele

私が試してみました

sed -e '1,/claudio/s/claudio/claudia/' nomi

しかし、グローバル置換を実行します。なぜですか?


ここを見てはlinuxtopia.org/online_books/linux_tool_guides/the_sed_faq/...もとinfo sed:(0,/REGEXP/:0の行番号は次のようにアドレス指定に使用することができる0,/REGEXP/ようになるsed。あまりにも最初の入力ラインにREGEXPに一致するようにしようとしますつまり、0,/REGEXP/あります同様1,/REGEXP/ADDR2は、入力の最初の行に一致する場合1 / REGEXP /フォームは、その範囲の開始と一致し、従って、レンジスパンを作るのに対し0、/ REGEXP /フォームは、それが範囲を終了するために検討することを除いて、正規表現の2回目の出現まで)
-jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomi行う必要があります
アダム・カッツ

回答:


23

GNUを使用している場合はsed、次を試してください。

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sed範囲を開始する行のまで、範囲を終了する正規表現のチェックを開始しません。

man sed(POSIXのmanページ、強調鉱山):

2つのアドレスを持つ編集コマンドは、包括的範囲を選択するものとします
介して第1のアドレスと一致する最初のパターンスペースから2番目に一致する
次のパターンスペース

を使用して awk

awkあなたが期待していたより多くの仕事の範囲:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

説明:

  • NR==1,/claudio/

    これは、行1で始まり、の最初の出現で終わる範囲ですclaudio

  • sub(/claudio/, "claudia")

    範囲内にいる間、この代替コマンドが実行されます。

  • 1

    行を印刷するためのこのawkの不可解な速記。


1
sedただし、GNUを前提としています。
ステファンシャゼル

@StéphaneChazelasPOSIXLY_CORRECTが設定されている場合にも機能しますが、それは私が望むほど意味がないと思います。回答が更新されました(BSDテストマシンが不足しています)。
John1024

awkは、IMO、ブール状態変数をシンプルにすることができますawk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
グレンはジャックマン

@glennjackmanまたはawk !x{x=sub(/claudio/,"claudia")}1

私はまた、成功した最初の部分で異なる区切り文字を使用することができなかった:0,/claudio/
パットマイロン

4

sedを使用した2つのプログラムの取り組みを次に示します。どちらもファイル全体を1つの文字列に読み取り、検索は最初の文字列のみを置き換えます。

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

解説付き:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file

3

GNUの新しいバージョンはsedこの-zオプションをサポートしています。

通常、sedは行末文字(改行またはキャリッジリターン)までの文字列を読み取って行を読み取ります。
GNUバージョンのsedでは、代わりに「NULL」文字を使用する機能がバージョン4.2.2に追加されました。これは、レコード区切り文字としてNULLを使用するファイルがある場合に役立ちます。一部のGNUユーティリティは、「find。-print0」や「grep -lZ」など、改行ではなくNULLを使用する出力を生成できます。

sed別の回線で作業する場合に、このオプションを使用できます。

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

返す

claudia
antonio
claudio
michele

1

awkフラグを使用して、置換が既に行われたかどうかを知ることができます。そうでない場合は、次の手順に進みます。

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele

1

少し遅延を設定するだけで本当に簡単です-信頼できない拡張機能に手を伸ばす必要はありません。

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

それは、1行目を2行目、2行目から3行目などに延期するだけです。

以下を印刷します。

claudia
antonio
claudio
michele

1

そしてもう一つのオプション

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

利点は、二重引用符を使用するため、内部で変数を使用できることです。

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi

1
ええ、あなたは正しいです。一般的な考え方は同じです。ただし、単一引用符を二重引用符に直接置き換えて、それが機能するかどうかを確認してください。悪魔は細部に横たわっています。この例では、これらはスペースと1つのエスケープです。以前の回答をこのように続ければ、誰かの時間を節約できると思います。そして、それが私が投稿を公開することにした理由です。
utom

1

これは、ホールドスペースなしで、すべての行をパターンスペースに連結せずに行うこともできます。

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

説明:「claudio」を見つけようとします。見つけたら、:xとの間の小さなprint-load-loopにジャンプしbxます。それ以外の場合は、次の行でスクリプトを印刷して再起動します。

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi

1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block

1
質問を読んで気になりましたか?
-don_crissti

1

Sumary

GNU構文:

sed '/claudio/{s//claudia/;:p;n;bp}' file

または(置換する単語を1回だけ使用するには:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

または、POSIX構文では:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

任意のsedで動作し、最初の行を見つけるのに必要なだけの行だけを処理します。最初の行にあり、1つの正規表現文字列のみを使用するため短いclaudio場合でも動作しclaudioます。

詳細

1行のみを変更するには、1行のみを選択する必要があります

1,/claudio/(質問から)を使用して選択します:

  • 最初の行から(無条件)
  • 次の文字列を含む行claudio
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

を含む行を選択するにclaudio、次を使用します。

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

また、ファイルの最初のもの のみを選択するには、claudioを使用します。

sed -n '/claudio/{p;q}' file
claudio 1

次に、その行でのみ置換を行うことができます。

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

これは、最初の行で正規表現に一致するものが複数ある場合でも、その行で最初に一致する正規表現のみを変更します。

もちろん、/claudio/正規表現は次のように簡略化できます。

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

そして、欠けている唯一のものは、変更されていない他のすべての行を印刷することです。

sed '/claudio/{s//claudia/;:p;n;bp}' file
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.