web-dev-qa-db-ja.com

sedを使用して、ファイル内の最初の出現のみを置換する方法は?

既存の#includeの前に追加のincludeディレクティブを使用して、多数のC++ソースファイルを更新したいと思います。この種のタスクでは、通常、sedで小さなbashスクリプトを使用してファイルを書き換えます。

sedを取得して、すべての出現を置換するのではなく、ファイル内の文字列の最初の出現のみを置換するにはどうすればよいですか?

私が使用する場合

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

すべての#includesを置き換えます。

同じことを達成するための代替案も歓迎します。

183
David Dibben
 # 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---

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

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

ソース

114
Ben Hoffstein

「Apple」の最初の出現を「Banana」に置き換えるだけのsedスクリプトを作成します

入力例:出力:

     Apple       Banana
     Orange      Orange
     Apple       Apple

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

sed '0,/Apple/{s/Apple/Banana/}' filename
252
tim
sed '0,/pattern/s/pattern/replacement/' filename

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

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

編集者注:両方ともGNUsedでのみ動作します。

53
Sushil

同様のことをするために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よりも理解しやすい:-)

23
richq

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
17
MikhailVS
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

このスクリプトの動作:1から最初の#includeまでの行(行1の後)で、行が#includeで始まる場合、指定された行を先頭に追加します。

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

12

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

sed s/#include/#include "newfile.h"\n#include/1
12
unexist

可能な解決策:

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

説明:

  • #includeが見つかるまで行を読み取り、これらの行を印刷してから新しいサイクルを開始します
  • 新しいインクルード行を挿入します
  • 行を読み込むループを入力します(デフォルトではsedもこれらの行を出力します)。ここからスクリプトの最初の部分には戻りません。
8
mitchnull

誰かがすべての行で最初に出現する文字を置き換えるためにここに来た場合(私のように)、これを使用します:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

たとえば、1を2に変更すると、代わりにすべての2番目のaのみを置き換えることができます。

3
FatihSarigol

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

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 and replace = blahで、grepが最初に見つかった行が5行目にある場合、最後のsedにパイプされるデータは5s /.*/ blah /になります。

3
Michael Edwards

別の提案として、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
2
timo

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

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

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スクリプトでコマンドを使用しているため、コマンドを二重引用符で囲みます。

2
Michael Cook

これはあなたのために働くかもしれません(GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

または、メモリに問題がない場合:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
2
potong

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

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

次に、awkで実行します。

awk -f awkscript headerfile.h > headerfilenew.h

ずさんなかもしれませんが、私はこれが初めてです。

2
wakingrufus

GNU sedの-zオプションを使用すると、ファイル全体を1行だけであるかのように処理できます。そうすれば、s/…/…/はファイル全体の最初の一致のみを置き換えます。要確認:s/…/…/は各行の最初の一致のみを置き換えますが、-zオプションを使用すると、sedはファイル全体を1行として扱います。

sed -z 's/#include/#include "newfile.h"\n#include'

一般的な場合、パターンスペースは1行だけでなくファイル全体を保持するため、sed式を書き換える必要があります。いくつかの例:

  • s/text.*//は、s/text[^\n]*//に書き換えることができます。 [^\n]はすべてに一致します除く改行文字。 [^\n]*は、textの後のすべてのシンボルと一致し、改行に到達します。
  • s/^text//は、s/(^|\n)text//に書き換えることができます。
  • s/text$//は、s/text(\n|$)//に書き換えることができます。
1
Socowi

新しいことはありませんが、おそらくもう少し具体的な答え: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

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

0x2200003
0

POSIXly(sedでも有効)、one regexのみが使用され、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.
0
Isaac

次のコマンドは、ファイル内の文字列の最初の出現を削除します。空の行も削除します。 xmlファイルで表示されますが、どのファイルでも機能します。

Xmlファイルを使用していて、タグを削除する場合に便利です。この例では、「isTag」タグの最初の出現を削除します。

コマンド:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

ソースファイル(source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

結果ファイル(output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps:Solaris SunOS 5.10(かなり古い)では動作しませんでしたが、Linux 2.6、sedバージョン4.1.5では動作します