sedでファイルの特定の部分だけ置換する方法

たくさんのファイルをsed -iしたい。…これだけならググればできる。

今回は、「1つのファイルの中に、置換元文字列にヒットする部分は沢山あるけど
そのうち特定のエリア(xmlの、特定の要素内)のみ置換したい」。

具体的にはこんなケースだ。

<A>
 <data>10</data>
</A>
<B>
 <data>10</data> ←こっちだけ20にしたい
</B>

sedでは行数の範囲を指定することはできる。

sed -i "4,6s/10/20/g" とすれば、4行目から6行目を対象にしてくれる。
とはいえ実際にはファイルによって該当エリアが何行目になるかは異なるから、
固定値で指定するわけにもいかない。

なので、ここの4,6部分を入れ子のコマンドにして、
grep -n -m1をさらにcutして行数だけにしたものを割り当てればいけそうだ。

■失敗例
最初はこんなことを考えたんですよ。

find * -exec sed -i "`grep -n -m1 "<B>" {} | cut -f1 -d:`,`grep -n -m1 "<\/B>" {} | cut -f1 -d:`s/10/20/g" {} \;

こうすると、入れ子になってるコマンドを先に実行しようとしてしまうので、
{}の部分がファイル名に展開されず
grep: {}: そのようなファイルやディレクトリはありません
と言われてしまう。

■結果
素直にシェルにしました。

コマンド:

find * -exec sh replace.sh {} \;

シェルの中身

#!/bin/sh
sed -i "`grep -n -m1 "<B>" $1 | cut -f1 -d:`,`grep -n -m1 "<\/B>" $1 | cut -f1 -d:`s/10/20/g" $1

なんか頑張ればシェルにしなくてもいけそうな気はしたけど、
頑張る理由もなかったので。仕事をこなすのが優先。