既存の#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---
または、必要に応じて: 編集者注:GNUsed
のみで動作します。
sed '0,/RE/s//to_that/' file
「Apple」の最初の出現を「Banana」に置き換えるだけのsedスクリプトを作成します
入力例:出力:
Apple Banana
Orange Orange
Apple Apple
これは簡単なスクリプトです。 編集者注:GNUsed
のみで動作します。
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
編集者注:両方ともGNUsed
でのみ動作します。
同様のことをするために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よりも理解しやすい:-)
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
#!/bin/sed -f
1,/^#include/ {
/^#include/i\
#include "newfile.h"
}
このスクリプトの動作:1から最初の#include
までの行(行1の後)で、行が#include
で始まる場合、指定された行を先頭に追加します。
ただし、最初の#include
が1行目にある場合、1行目と次の#include
の両方に行が追加されます。 GNU sed
を使用している場合は、(0,/^#include/
の代わりに)1,
が正しいことを行う拡張機能があります。
最後に出現回数を追加するだけです:
sed s/#include/#include "newfile.h"\n#include/1
可能な解決策:
/#include/!{p;d;}
i\
#include "newfile.h"
:
n
b
説明:
誰かがすべての行で最初に出現する文字を置き換えるためにここに来た場合(私のように)、これを使用します:
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のみを置き換えることができます。
私はこれが古い投稿であることを知っていますが、使用した解決策がありました:
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 /になります。
別の提案として、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
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
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スクリプトでコマンドを使用しているため、コマンドを二重引用符で囲みます。
これはあなたのために働くかもしれません(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...
私はawkスクリプトでこれを行います:
BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}
END {}
次に、awkで実行します。
awk -f awkscript headerfile.h > headerfilenew.h
ずさんなかもしれませんが、私はこれが初めてです。
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|$)//
に書き換えることができます。新しいことはありませんが、おそらくもう少し具体的な答え: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 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.
次のコマンドは、ファイル内の文字列の最初の出現を削除します。空の行も削除します。 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では動作します