複数のテキストファイルに同じ修正を適用する

似たような複数のテキストファイルに対して、全く同じ修正を適用する。設定ファイルの類でたまにそういう作業が発生する事があります。最近だと巷で噂の httpoxy 対策とか。Apacheの .htaccess にproxy削除の1行を追加したいんだけど、サイトを大量を抱えていて、一々エディタで開いて追記して回るのも大変だ〜、なんて話もあるかと思います。というか私がそんな作業に遭遇しました…

# 要点

手作業は面倒だし、間違えるのが怖い。そこで自動化を図る。

- patch コマンドを利用。どのファイルでも同じ位置へ適用する作業でないと、ちょっと危ないかも知れませんが。今回のような話であれば大丈夫です。
- 確認には「差分の一致」を調べる事で、人間の注意力が必要な場面を極力減らす。

テキストの追記や修正は sed を使っても可能ですが、少々面倒なんですよね。それだけに応用範囲も広いんですが。今回は、Linux初心者だと使う機会が少ない（と思う） patch コマンドを使いました。古くからUNIX系OSを使っているパワーユーザにとっては、既に良く御存知の手順です。

# 詳細

## 作業対象

次の3つのファイルを作業対象とします。

```
$ for pf in hoge tara ponn; do echo ===== htaccess-$pf =====; cat htaccess-$pf; echo; done
===== htaccess-hoge =====
<VirtualHost *:80>
    ServerAdmin webmaster@hoge.example.com
    DocumentRoot /www/docs/hoge.example.com
    ServerName hoge.example.com
    ErrorLog /var/log/httpd/hoge.example.com/error_log
    TransferLog /var/log/httpd/hoge.example.com/access_log

    # sample comment, first
</VirtualHost>

===== htaccess-tara =====
<VirtualHost *:80>
    ServerAdmin webmaster@tara.example.com
    DocumentRoot /www/docs/tara.example.com
    ServerName tara.example.com
    ErrorLog /var/log/httpd/tara.example.com/error_log
    TransferLog /var/log/httpd/tara.example.com/access_log

    # sample comment, first
    # sample comment, second
</VirtualHost>

===== htaccess-ponn =====
<VirtualHost *:80>
    ServerAdmin webmaster@ponn.example.com
    DocumentRoot /www/docs/ponn.example.com
    ServerName ponn.example.com
    ErrorLog /var/log/httpd/ponn.example.com/error_log
    TransferLog /var/log/httpd/ponn.example.com/access_log

    # sample comment, first
    # sample comment, second
    # sample comment, third
</VirtualHost>
```

これの「所定の位置」に、次の内容を追加します。
```
    # 2016.08.05.Fri (httpoxy)
    RequestHeader unset Proxy
```

## 手順

### 最初のファイルを手作業で修正

```
$ vi htaccess-hoge
$ svn di --diff-cmd diff -x -U2
Index: htaccess-hoge
===================================================================
--- htaccess-hoge       (revision 3)
+++ htaccess-hoge       (working copy)
@@ -6,4 +6,7 @@
     TransferLog /var/log/httpd/hoge.example.com/access_log

+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
     # sample comment, first
 </VirtualHost>
```

### 差分を取り出す

```
$ svn di --diff-cmd diff -x -U0 >htaccess.diff
$ cat htaccess.diff
Index: htaccess-hoge
===================================================================
--- htaccess-hoge (revision 3)
+++ htaccess-hoge (working copy)
@@ -7,0 +8,3 @@
+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
```

### 他のファイルに適用

```
$ for pf in tara ponn; do patch htaccess-$pf <htaccess.diff; done
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|Index: htaccess-hoge
|===================================================================
|--- htaccess-hoge      (revision 3)
|+++ htaccess-hoge      (working copy)
--------------------------
Patching file htaccess-tara using Plan A...
Hunk #1 succeeded at 8.
done
Hmm...  Looks like a unified diff to me...
The text leading up to this was:
--------------------------
|Index: htaccess-hoge
|===================================================================
|--- htaccess-hoge      (revision 3)
|+++ htaccess-hoge      (working copy)
--------------------------
Patching file htaccess-ponn using Plan A...
Hunk #1 succeeded at 8.
done
```

パッチファイルのヘッダに示されているファイル名と、指示した適用先ファイル名が異なっているので報告してきますが、無視！

### 確認

確認は大事です。適用した全ファイルの差分が、最初に手作業で実施した ```htaccess-hoge``` の差分と一致する事を確認します。

で、その差分の取り方に一工夫。詳細は下記。bashのプロセス置換を利用しています。

```
$ for pf in tara ponn; do diff -sU0 <(tail -n+5 htaccess.diff) <(svn di --diff-cmd diff -x -U0 htaccess-$pf | tail -n+5) --label "htaccess-hoge の差分" --label "htaccess-$pf の差分"; done
Files htaccess-hoge の差分 and htaccess-tara の差分 are identical
Files htaccess-hoge の差分 and htaccess-ponn の差分 are identical
```

#### コマンドの意味

長いですが、丁寧に追えば理解できるでしょう。

まず ```diff -sU0 <FILE1> <FILE2> --label <LABEL1> --label <LABEL2>``` について。単純な、diffコマンドのオプション指定です。何の捻りもありませんが、ここで下記のように工夫しました:

1. ```<FILE1> <FILE2>``` の部分に、差分の中の必要部分を取り出して与えています。
2. ```<LABEL1> <LABEL2>``` の部分には「差分なんだよ」を意味する言葉を置いています。これで表示結果が見易くなります。
3. diff のオプション -s について。一致した時には一致した事を表す文言を表示します。エビデンスを残す必要がある場合には非常に助かります。

```<FILE1>``` には、最初の差分 htaccess.diff の内、ファイル名を表している部分を削除して与えています。

```
$ cat htaccess.diff
Index: htaccess-hoge
===================================================================
--- htaccess-hoge       (revision 3)
+++ htaccess-hoge       (working copy)
@@ -7,0 +8,3 @@
+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
$ tail -n+5 htaccess.diff
@@ -7,0 +8,3 @@
+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
```

```<FILE2>``` の部分も同様ですね。Subversionで取得した差分の内、ファイル名を表している部分を削除して与えています。

```
$ svn di --diff-cmd diff -x -U0 htaccess-tara
Index: htaccess-tara
===================================================================
--- htaccess-tara       (revision 3)
+++ htaccess-tara       (working copy)
@@ -7,0 +8,3 @@
+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
$ svn di --diff-cmd diff -x -U0 htaccess-tara | tail -n+5
@@ -7,0 +8,3 @@
+    # 2016.08.05.Fri (httpoxy)
+    RequestHeader unset Proxy
+
```

最後は、これを対象ファイルの分だけループします。ループの方法やファイル名の与え方などは、状況に合わせて調整して下さい。

### 不要ファイルを削除

問題無く修正できた事を確認したら、不要なファイルを削除します。

- 最初の差分ファイル
- patchコマンドが自動生成したバックアップファイル

バージョン管理ツールを使用していれば、これらは管理外のファイルとして現れます。それを捕まえれば、ここも自動化できます。

```
$ svn st | awk '/^\?/ {print $2}' | xargs rm -v
htaccess-ponn.orig
htaccess-tara.orig
htaccess.diff
```

最後にSubversionにcommitすれば完了です。ここまで、手作業での修正は最初のファイルだけでした。後は自動。やった :+1: