2-6 ファイルの中の一部を書き換える(置換する)


 データの一部のみを置換したい場合に便利なコマンドとしてsedコマンドがあります。sedコマンドは置換以外にも、検索、文字列の消去(空データとの置換)、特定の行の抽出など様々な用途に使える非常に汎用性の高い便利なコマンドです。ただ何でもできる分、コマンドの構文が少しやっかいです。しかし一度シェルスクリプトを書いてしまえば、それを何回も使えるので、一度作っておくと後々役に立ちます。

基本

 例えば、fileの中身が

Today is a bad day for me. I am in a bad mood now.
Are you in a bad mood now?
だとします。このfileに対して下記のようなシェルスクリプトを実行してみます。
#!/bin/sh
cat file | sed "s/bad/good/"
すると次のような出力が返ってくると思います。
Today is a good day for me. I am in a bad mood now.
Are you in a good mood now?
みればわかるように各行の初めにある「bad」が「good」に置換されていると思います。sedコマンドは"s/ほげ/ほにゃらか"と書くことで「ほげ」を「ほにゃらか」に置換してくれます。もし各行の最初のbadだけでなく、ファイル中の全てのbadをgoodに変えたかったら、次のようにgをコマンドの最後につければOKです。
#!/bin/sh
cat file | sed "s/bad/good/g"
すると出力は以下のようになり、気分もよくなるはずです。
Today is a good day for me. I am in a good mood now.
Are you in a good mood now?
ちなみに上記のシェルスクリプトではパイプライン処理をしてcatの出力をsedに与えましたが、当然以下のようにしても全く同じ機能をします。
#!/bin/sh
sed "s/bad/good/" file
#!/bin/sh
sed "s/bad/good/g" file

さらにこんなこともできる!

①特定の行や部分のみを置換したい
今、下のようなfile2があったとします。

Kaeru pyoko pyoko mipyokopyoko.
Awasete pyoko pyoko mipyokopyoko.
1行目の「mikpyokopyoko」はそのままにして、2行目の間違っているもの「mipyokopyoko」を正しい「mupyokopyoko」に置換したいとします。その場合次のようなシェルスクリプトを書けばOKです。
#!/bin/sh
cat file2 | sed "2s/mipyokopyoko/mupyokopyoko/g"
sの前につけた「2」は「2行目についてだけ置換を行う」という指示です。こうやれば、1行目の「mipyokopyoko」を保持したまま1行目の「mipyokopyoko」を「mupyokopyoko」に置換できます。ちなみに、下記のようなコマンドでもうまくいきます。
#!/bin/sh
cat file2 | sed "/Awasete/s/mipyokopyoko/mupyokopyoko/g"
ダブルクオーテーションの初めの項目「Awasete」は「Awaseteという文字列がある行に関して処理を行う」という意味です。/s/のあとの1つめの項目「mipyokopyoko」は置換前の文字列、2つめの項目「mupyokopyoko」は置換後の文字列です。

②複雑なパターンの指定(正規表現)
上記の例では置換する前の文字列が「mipyokopyoko」のように確定している場合でした。しかし実用上は、「文字列は確定していないけど、ここにある数文字を置換したい」というような場合があります。例えばflagを記憶しているファイル

flag=1
というデータファイルfile4があったとします。flagの値は0だったり1だったりします。このflagの値が0であろうが1であろうが、必ず1に変更するシェルスクリプトが欲しい場合があったりします。その場合次のようにシェルスクリプトを書きます。
#!/bin/sh
cat file4 | sed "s/flag=./flag=1/g"
ここで「.」とは「何かしら1文字」という意味です。つまり上のシェルでは、「flag=+何かしら1文字」を「flag=1」に置換しろ、という意味です。こうすればflagの値が0であろうが1であろうが、シェルスクリプトを実行することで強制的に1に変換できます。このようにシンプルな文字よりも少し複雑な意味を持つ記号を正規表現といいます。sedコマンドを使うときに便利な正規表現をいかにいくつかあげます。

. : 何かしら1文字
* : 直前の1文字の0回以上の繰り返し
[] : 括弧の中のうちの任意の1文字
\ :正規表現に使われる記号をフツーの文字としてみなす

例えば各マシンの状態等を記憶しているファイル
PC1=yes
PC2=no
PC3=yes
というデータファイルfile5があったとします。ここでPC1の状態を「yes」に変更するシェルを考えます。直前の状態がyesの場合とnoの場合があり、文字数が異なるので、*の正規表現を使う必要があります。
#!/bin/sh
cat file5 | sed "s/PC1=.*/PC1=yes/g"
「.*」は.(ドット)が「何か1文字」を表し、*(スター)が「直前の文字(どっと)が0回以上繰り返し」なので、結局「何かしらの文字が1文字以上」という意味です。なので初期状態が「PC1=yes」だろうが「PC1=no」だろうが「PC1=unknow」だろうが、このシェルスクリプトを実行すると「PC1=yes」に置換された出力になります。

 ちなみに、上記のflagが書いてあるファイルの書き換え(file4の書き換え)は正規表現[]を使えば以下のようにも書き換えられます。

#!/bin/sh
cat file4 | sed "s/flag=[01]/flag=1/g"
flagの数値が0か1だけでなく、0から9の値をとりうる場合は、[0123456789]などと書かずとも、以下のように書けばOKです。
#!/bin/sh
cat file4 | sed "s/flag=[0-9]/flag=1/g"
(任意のアルファベットも、[a-z]と表現できます。)

便利な応用例

下記のようなプログラム用のinputファイルinput.f90があったとします(僕はfortranを使っているので、fortran形式でinputファイルを書いています)。
integer,parameter::&
  N_x = 100 ,&
  N_y = 100 ,&
real(kind=8),parameter::&
  U = 0.3_8 ,&
  J = 0.5_8
このinputファイルinput.f90の中の Uの部分だけ値を1.0に変えたファイルinput_2.f90をつくりたいとします。その場合次のようにシェルスクリプトを書きます。
#!/bin/sh 
cat input.f90 | sed "s/U =.*_8,/U = 1.0_8,/" > input_2.f90
そうすれば、input_2.f90に以下のようなデータが出てきて、ちゃんとUの値だけが書き換わります。
integer,parameter::&
  N_x = 100 ,&
  N_y = 100 ,&
real(kind=8),parameter::&
  U = 1.0_8 ,&
  J = 0.5_8
一見この処理はシェルスクリプトを書くより、手で書き換えた方が早い気がします。しかし次の節を見ればわかるように、パラメータを少しずつ変化させて数値計算をする場合非常に便利になります。特に1つのパラメータ値に関する数値計算のコストが高く、「U=1での計算をやるフォルダとinputファイル」、「U=1.1での計算をやるフォルダとinputファイル」....のようにたくさんのフォルダを作って数値計算を行う場合、たくさんフォルダを作ってたりinputファイルをいちいち書き換えてコピーしたりしてるのは面倒ですし、何より時間と才能の無駄遣いです。シェルスクリプトを使えばこの操作がかなり楽にできます。次の節ではこの方法を紹介します。
前項目へ  次項目へ  目次へ戻る

E-mail : endo cat.phys.s.u-tokyo.ac.jp