sedを使用して、ファイル内の最初の出現のみを置換する方法は?
-
02-07-2019 - |
質問
既存の#includeの前に、追加のincludeディレクティブを使用して、多数のC ++ソースファイルを更新したいと思います。この種のタスクでは、通常、sedで小さなbashスクリプトを使用してファイルを書き換えます。
すべての出現を置換するのではなく、ファイル内の文字列の最初の出現のみを置換するにはsed
を取得するにはどうすればよいですか?
使用する場合
sed s/#include/#include "newfile.h"\n#include/
すべての#includesを置き換えます。
同じことを達成するための代替案も歓迎します。
解決
# 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,/RE/s//to_that/' file
他のヒント
<!> quot; Apple <!> quot;の最初の出現のみを置き換えるsedスクリプトを記述します。 by <!> quot; Banana <!> quot;
入力例:出力:
Apple Banana
Orange Orange
Apple Apple
これは簡単なスクリプトです。編集者のメモ: GNU sed
のみで動作します。
sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename
これでうまくいきました。
例
sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt
編集者のメモ:どちらも GNU sed
でのみ動作します。
説明で補完された多くの有用な既存の回答の概要:
ここの例では、単純化されたユースケースを使用しています。最初の一致する行でのみ単語「foo」を「bar」に置き換えます。
ANSI C引用符付き文字列($'...'
)は、サンプル入力行を提供します。bash
、ksh
、またはzsh
がシェルと見なされます。
GNU sed
のみ:
Ben Hoffsteinのanwswer は、GNUが次の2アドレス形式を許可する0,/re/
のPOSIX仕様:re
(1,/re/
はここでは任意の正規表現を表します)。
//
により、正規表現は最初の行でも に一致します。つまり、このようなアドレスは、1行目からs/.../.../
に一致する行を含む1行目までの範囲を作成します-s
が1行目または後続の行で発生するかどうか。
- これをPOSIX準拠のフォーム
foo
と比較すると、最初の行から後続のt
に一致する行までを含む範囲が作成されます em>行;言い換えれば、この 行で最初に発生した-e
一致を検出せず、省略表現の使用を禁止します1 s/foo/bar/
最近使用した正規表現を再利用します(次のポイントを参照)。 [1]
1,//
アドレスを same 正規表現を使用する2
(置換)呼び出しと組み合わせると、コマンドは実質的に first s//
と一致するem>行。
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
は、最近適用された正規表現を再利用するための便利なショートカットを提供します: 空区切り文字ペア、$'1bar\n2bar'
。
$ 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
BSD(macOS)1
などのPOSIX機能のみの/foo/
( GNU s/foo/bar/
でも機能します):
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
は使用できず、フォームsed: first RE may not be empty
が最初の行(上記を参照)で発生した場合sed: -e expression #1, char 0: no previous regular expression
を検出しないため、 1行目の特別な処理が必要です。
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回使用されます。1回は範囲のエンドポイント用で、1回は<=>呼び出しで使用されます。どちらの場合も、正規表現<=>は暗黙的に再利用されるため、複製する必要がなく、コードの保守性が向上します。
-
POSIX <=>は、ここで<=>の場合のように、ラベルの名前の後、または省略した場合など、特定の関数の後に実際の改行を必要とします。スクリプトを複数の<=>オプションに戦略的に分割することは、実際の改行を使用する代わりに使用できます。通常は改行が必要な各<=>スクリプトチャンクを終了します。
<=>は、1行目で見つかった場合にのみ<=>を置き換えます。 その場合、<=>はスクリプトの最後に分岐します(その行の残りのコマンドをスキップします)。 (<=>関数は、最新の<=>呼び出しが実際の置換を実行した場合にのみラベルに分岐します。ここにあるように、ラベルがない場合は、スクリプトの終わりに分岐します。)
それが発生すると、通常は2行目から最初に出現する を検出する範囲アドレス<=>は一致しなくなり、範囲は一致しなくなります現在の行が既に<=>であるときにアドレスが評価されるため、処理されます。
逆に、1行目に一致がない場合は、<=> が入力され、find真の最初の一致。
最終的な効果は、GNU <=>の<=>と同じです。最初の行で発生したかどうかにかかわらず、最初の発生のみが置き換えられます。
非範囲アプローチ
potongの答えは、 ループテクニック範囲の必要性をバイパスします。彼は GNU <=>構文を使用しているため、 POSIX準拠の同等物は次のとおりです。
ループテクニック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.61803 では、<=>の有無、および後続の<=>:
-<=>は<=>を生成します。つまり、行番号<=>が最初の行と一致するため both 行が更新され、正規表現<=>-範囲の終わり-は next 行。したがって、この場合は両方の行が選択され、両方で<=>置換が実行されます。
-<=> failes :<=>(BSD / macOS)および<=>(GNU)で、最初の行が処理されているため(行番号<=>開始のため)範囲)、正規表現はまだ適用されていないため、<=>は何も参照しません。
GNU <=>の特別な<=>構文を除いて、行番号で始まる任意の範囲は、<=>の使用を事実上排除します。
awkを使用して同様のことを行うことができます。
awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c
説明:
/#include/ && !done
行が<!> quot; #include <!> quot;に一致する場合、{}の間にアクションステートメントを実行します。まだ処理していません。
{print "#include \"newfile.h\""; done=1;}
これは#include <!> quot; newfile.h <!> quot;を出力します。引用符をエスケープする必要があります。次に、done変数を1に設定して、インクルードを追加しません。
1;
これは、<!> quot;行を出力する<!> quot; -空のアクションはデフォルトで$ 0を印刷し、行全体を印刷します。ライナーは1つで、sed IMOよりも理解しやすいです:-)
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 '}'
ex:
sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
最後に出現回数を追加するだけです:
sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
このスクリプトの仕組み:1から最初の#include
までの行(行1の後)で、行がsed
で始まる場合、指定された行を追加します。
ただし、最初の0,/^#include/
が1行目にある場合、1行目と次の1,
の両方に行が追加されます。 GNU <=>を使用している場合、(<=>の代わりに)<=>が正しいことを行う拡張機能があります。
可能な解決策:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
説明:
- #includeが見つかるまで行を読み取り、これらの行を印刷してから新しいサイクルを開始します
- 新しいインクルード行を挿入
- 行を読み込むだけのループに入ります(デフォルトではsedもこれらの行を出力します)。ここからスクリプトの最初の部分には戻りません
これは古い投稿であることは知っていますが、使用していた解決策がありました:
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 /です。
(私のような)すべての行で最初に現れる文字を置き換えるために誰かがここに来た場合、これを使用します:
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のみを置き換えることができます。
iはawkスクリプトを使用してこれを行います。
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}
END {}
それからawkで実行します:
awk -f awkscript headerfile.h > headerfilenew.h
だらしないかもしれない、私はこれに新しい。
別の提案として、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
ついに、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スクリプトでコマンドを使用しているため、コマンドを二重引用符で囲みます。
FreeBSD ed
を使用し、include
の<!> quot; no match <!> quot;を避けます。処理するファイルに<=>ステートメントがない場合のエラー:
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
これはあなたのために働くかもしれません(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...
GNU sedの-z
オプションを使用すると、ファイル全体を1行だけのように処理できます。この方法では、s/…/…/
はファイル全体の最初の一致のみを置き換えます。注意:sed
は各行の最初の一致のみを置き換えますが、s/text.*//
オプションを使用すると、s/text[^\n]*//
はファイル全体を1行として扱います。
sed -z 's/#include/#include "newfile.h"\n#include'
一般的なケースでは、パターンスペースが1行だけでなくファイル全体を保持するため、sed式を書き換える必要があります。いくつかの例:
-
[^\n]
は[^\n]*
に書き換えることができます。text
は、改行文字を除くすべての に一致します。s/^text//
は、改行に達するまで、s/(^|\n)text//
の後のすべてのシンボルに一致します。 -
s/text$//
はs/text(\n|$)//
に書き換えることができます。 - <=>は<=>に書き換えることができます。
次のコマンドは、ファイル内の文字列の最初の出現を削除します。空の行も削除します。 xmlファイルで表示されますが、どのファイルでも機能します。
xmlファイルを使用していて、タグを削除する場合に便利です。この例では、<!> quot; isTag <!> quotの最初の出現を削除します。タグ。
コマンド:
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では動作します
新しいものはありませんが、おそらくもう少し具体的な答え: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
POSIXly(sedでも有効)、使用される正規表現は one のみ、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.