270

I would like to update a large number of C++ source files with an extra include directive before any existing #includes. For this sort of task, I normally use a small bash script with sed to re-write the file.

How do I get sed to replace just the first occurrence of a string in a file rather than replacing every occurrence?

If I use

sed s/#include/#include "newfile.h"\n#include/

it replaces all #includes.

Alternative suggestions to achieve the same thing are also welcome.

4

24 に答える 24

342

最初に出現したsed「Apple」のみを「Banana」に置き換えるスクリプト

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

これは簡単なスクリプトです:編集者注: GNU sedのみで動作します。

sed '0,/Apple/{s/Apple/Banana/}' input_filename

最初の 2 つのパラメーター0/Apple/は、範囲指定子です。は、そのs/Apple/Banana/範囲内で実行されるものです。0したがって、この場合は「先頭の ( ) から の最初のインスタンスまでの範囲内で、にApple置き換えます。最初のものだけが置き換えAppleられます。BananaApple

背景: 従来sedの範囲指定子は「ここで開始」および「ここで終了」(包括的) です。ただし、最下位の「開始」は最初の行 (1 行目) であり、「ここで終了」が正規表現の場合は、「開始」の次の行でのみ照合が試行されるため、可能な限り早い終了は行です。 2. したがって、範囲は包括的であるため、最小範囲は「2 行」であり、最小開始範囲は行 1 と行 2 の両方です (つまり、行 1 に出現がある場合、行 2 の出現も変更されます。この場合は望ましくありません) )。GNUsed は、開始を「疑似」として指定できるようにする独自の拡張機能を追加してline 0、範囲の終わりをline 1「最初の行のみ」の範囲にできるようにします。

または簡略化されたバージョン (空の RE のようなもの//は、その前に指定されたものを再利用することを意味するため、これは同等です):

sed '0,/Apple/{s//Banana/}' input_filename

また、コマンドの中括弧はオプションであるsため、これも同等です。

sed '0,/Apple/s//Banana/' input_filename

これらはすべて GNUsedでのみ機能します。

homebrew を使用して OS X に GNU sed をインストールすることもできますbrew install gnu-sed

于 2012-02-26T13:27:07.140 に答える
168
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

または、必要に応じて:編集者注: GNU sedのみで動作します。

sed '0,/foo/s//bar/' file 

ソース

于 2008-09-29T12:29:27.737 に答える
62
sed '0,/pattern/s/pattern/replacement/' filename

これは私のために働いた。

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

編集者注: どちらもGNU sedのみで動作します。

于 2010-08-17T12:30:50.230 に答える
59

説明で補完された、多くの役立つ既存の回答概要

ここでの例では、単純化された使用例を使用しています。最初の一致する行のみで単語「foo」を「bar」に置き換えます。ANSI C で引用された文字列 ( )
を 使用してサンプル入力行を提供しているため、、、またはがシェルと見なされます。$'...'bashkshzsh


GNU sedのみ:

Ben Hoffstein の回答は、GNUが次の2 アドレス形式を許可するPOSIX 仕様の拡張を提供していることを示しています: (ここでは任意の正規表現を表します)。sed0,/re/re

0,/re/正規表現が最初の行でも一致するようにします。言い換えれば、このようなアドレスは、最初の行から一致する行までの範囲を作成しreますre

  • これを POSIX 準拠の form と比較してください。これは、最初の行から後続1,/re/の行で一致する行まで、およびその行を含む範囲を作成しreます。言い換えれば、最初の行で一致が発生した場合、これは最初の一致を検出せず、最近使用された正規表現を再利用するための短縮形の使用も防止します(次のポイントを参照)。1re//

0,/re/アドレスを、同じs/.../.../正規表現を使用する (置換) 呼び出しと組み合わせると、コマンドは、一致する最初の行でのみ効果的に置換を実行します。は、最近適用された正規表現 を再利用するための便利なショートカットを提供します:空の区切り文字ペア.re
sed//

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

sedBSD (macOS) などの POSIX 機能のみ( sedGNU でも動作しますsed):

0,/re/は使用できず、たまたま最初の行で発生してもフォーム1,/re/は検出されないため(上記を参照)、 1 行目の特別な処理が必要ですre

MikhailVS の回答では、この手法について言及しており、ここに具体的な例を示しています。

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

ノート:

  • ここでは、空の正規表現の//ショートカットが 2 回使用されていsます。どちらの場合も、正規表現fooは暗黙的に再利用されるため、重複する必要がなくなり、コードが短くなり、保守しやすくなります。

  • POSIX では、ここsedの場合のように、特定の関数の後に実際の改行が必要です。たとえば、ラベルの名前やその省略の後にさえありtます。-eスクリプトを戦略的に複数のオプションに分割することは、実際の改行を使用する代わりの方法-eです。通常、改行が必要な場所で各スクリプト チャンクを終了します。

1 s/foo/bar/fooそこにある場合は、最初の行のみを置き換えます。その場合tは、スクリプトの最後に分岐します (行の残りのコマンドをスキップします)。(t関数は、最新のs呼び出しが実際の置換を実行した場合にのみ、ラベルに分岐します。この場合のように、ラベルがない場合は、スクリプトの最後に分岐します)。

その場合、1,//通常は行 2 から始まる最初のオカレンスを検索する範囲 address が一致せず、範囲は処理されません。これは、現在の行が既に であるときにアドレスが評価されるため2です。

逆に、最初の行に一致がない場合1,// 入力され、真の最初の一致が検索されます。

最終的な効果は GNUsedの場合と同じ0,/re/です。最初の行で発生するか他の行で発生するかに関係なく、最初に発生したものだけが置き換えられます。


非範囲アプローチ

potong's answerは、 range の必要性を回避するループ手法を示しています。彼はGNU構文を使用しているため、POSIX準拠の同等のものは次のとおりです。 sed

ループ手法 1: 最初の一致で置換を実行し、残りの行をそのまま出力するループに入ります

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

小さいファイルのみのループ手法 2 :入力全体をメモリに読み込み、それに対して単一の置換を実行します

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.618031,/re/は、 がある場合、後続の がある場合、およびない場合の例を示していますs//

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'利回り$'1bar\n2bar'; つまり、両方の行が更新されました。これは、行番号1が最初の行と一致し、正規表現(範囲の末尾) が次の/foo/から開始するためにのみ検索されるためです。したがって、この場合は両方の行が選択され、両方に対して置換が実行されます。s/foo/bar/
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' 失敗: sed: first RE may not be empty(BSD/macOS) およびsed: -e expression #1, char 0: no previous regular expression(GNU) では、最初の行が処理されている時点で (1範囲の開始行番号のため)、まだ正規表現が適用されていないため//、何も参照していません。GNUの特別な構文
    除いて、行番号で始まる範囲は事実上. sed0,/re///
于 2015-10-29T14:07:39.637 に答える
31

awk を使用して同様のことを行うことができます..

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

説明:

/#include/ && !done

行が「#include」に一致し、まだ処理していない場合、{} の間のアクション ステートメントを実行します。

{print "#include \"newfile.h\""; done=1;}

これは #include "newfile.h" を出力します。引用符をエスケープする必要があります。次に、done 変数を 1 に設定して、これ以上インクルードを追加しません。

1;

これは「行を出力する」ことを意味します - 空のアクションはデフォルトで $0 を出力し、行全体を出力します。1 つのライナーで、sed IMO よりも理解しやすい :-)

于 2008-09-29T12:29:39.887 に答える
23

linuxtopia sed FAQに関する非常に包括的な回答のコレクション。また、人々が提供したいくつかの回答は、非GNUバージョンのsedでは機能しないことも強調しています。

sed '0,/RE/s//to_that/' file

非GNUバージョンでは

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

ただし、このバージョンは gnu sed では動作しません。

両方で動作するバージョンは次のとおりです。

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

元:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
于 2012-07-12T19:03:55.120 に答える
12
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

このスクリプトのしくみ:1から最初の#include行(1行目以降)の行で、行が。で始まる場合は#include、指定した行を先頭に追加します。

ただし、最初#includeの行が1行目にある場合は、1行目と次の行の両方#includeに行が付加されます。GNUを使用している場合は、 (の代わりに)正しいことを行うsed拡張機能があります。0,/^#include/1,

于 2008-09-29T12:41:52.137 に答える
10

最後に出現回数を追加するだけです:

sed s/#include/#include "newfile.h"\n#include/1
于 2008-09-29T12:30:36.453 に答える
7

考えられる解決策:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :a
    n
    ba

説明:

  • #includeが見つかるまで行を読み取り、これらの行を印刷してから、新しいサイクルを開始します
  • 新しいインクルード行を挿入します
  • 行を読み取るだけのループに入ります(デフォルトでは、sedもこれらの行を出力します)。ここからスクリプトの最初の部分に戻ることはありません。
于 2008-09-29T12:38:12.893 に答える
6

これは古い投稿であることは知っていますが、以前使用していた解決策がありました。

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

基本的に grep を使用して最初に出現したものを出力し、そこで停止します。さらに、行番号を出力し5:lineます。それを sed にパイプして : とその後を削除すると、行番号だけが残ります。それを sed にパイプして、最後の番号に s/.*/replace を追加します。これにより、最後の sed にパイプされてファイルのスクリプトとして実行される 1 行のスクリプトが生成されます。

したがって、regex =#includeおよび replace =blahであり、grep が検出した最初のオカレンスが 5 行目にある場合、最後の sed にパイプされたデータは5s/.*/blah/.

最初の出現が最初の行にある場合でも機能します。

于 2015-05-24T04:32:20.833 に答える
2

私はこれをawkスクリプトで行います:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

次に、awkで実行します。

awk -f awkscript headerfile.h > headerfilenew.h

ずさんかもしれません、私はこれに不慣れです。

于 2008-11-18T02:24:04.540 に答える
2

FreeBSD を使用し、処理するファイルにステートメントがない場合の の「一致なし」エラーedを回避します。edinclude

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF
于 2012-05-16T10:45:24.420 に答える
2

別の提案として、コマンドを確認することをお勧めしますed

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF
于 2011-04-28T12:52:54.713 に答える
2

RSS フィードの各項目に一意のタイムスタンプを挿入するために使用される Bash スクリプトで、ようやくこれが機能するようになりました。

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

最初のオカレンスのみが変更されます。

${nowms}は Perl スクリプトによって設定されるミリ秒単位の時間です。 は$counterスクリプト内のループ制御に使用されるカウンターであり\、コマンドを次の行に継続できるようにします。

ファイルが読み込まれ、stdout が作業ファイルにリダイレクトされます。

私が理解している方法で1,/====RSSpermalink====/は、範囲制限を設定して停止するタイミングを sed に伝えs/====RSSpermalink====/${nowms}/、最初の文字列を 2 番目の文字列に置き換えるおなじみの sed コマンドです。

私の場合、変数を含む Bash スクリプトでコマンドを使用しているため、コマンドを二重引用符で囲みます。

于 2011-11-17T19:41:53.680 に答える
0

POSIXly (sed でも有効)、使用される正規表現は 1だけで、必要なメモリは 1 行だけです (通常どおり):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

説明:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
于 2018-10-11T05:37:15.870 に答える
0

新しいことは何もありませんが、おそらくもう少し具体的な答えです:sed -rn '0,/foo(bar).*/ s%%\1%p'

例: 次xwininfo -name unity-launcherのような出力が生成されます。

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

ウィンドウ ID を抽出すると、以下がxwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'生成されます。

0x2200003
于 2017-01-17T00:37:46.373 に答える