特定のディレクトリ配下の特定文字列を一括して置換する便利スクリプト


## はじめに

なぜ急にこんな事をしたくなったのか。
とあるファイルのファイル名を修正しました。
ところが、調べてみるとそのファイルを参照しているコードがたくさんあることに気づき、すべて置換しないといけなくなった。
という背景がありました。

## 例題

次のように、ファイルをリネームしました。

CreateIten.php -> CreateItem.php

すると、itemディレクトリ配下でCreateIten.phpを使っているコードがたくさん見つかりました。
このコードを一括で置換したいです。

### とりあえず調査してみる

```bash
[riki@hoge]$ find ./ -type f | xargs grep CreateIten.php
./item/Item_13.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_4.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_9.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_19.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_20.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_18.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_14.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_6.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_12.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_11.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_10.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_17.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_7.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_1.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_2.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_16.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_3.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_8.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_15.php:require_once __DIR__.'/../CreateIten.php';
./item/Item.php:require_once __DIR__.'/../CreateIten.php';
./item/Item_5.php:require_once __DIR__.'/../CreateIten.php';
```

なるほど、どうやらitemディレクトリ配下で使われているみたいだ。

* 因みに・・@heliac2000さんからコメントで頂いたテクニックとして、grepの`-l`オプションを使ってファイル名だけを出力することも可能だそうです

`find ./ -type f | xargs grep CreateIten.php`

(このようにすれば、こここから↓のawkとかを使った処理は不要となります)

### とりあえず、対象のファイルを洗い出したい

↑のコマンドだと、同じコード内に2箇所以上ある場合、同じファイルが2つ出てくる。
対象ファイルをユニークで洗い出したいので、下記のステップを踏めば出せそうだ。

* まずはgrepの結果からファイル名を出力
	* ↑の結果を、`:`区切りにして、1列目を出力したい
* 重複を排除する
	* sortして、uniqかければOKそう

実践！

```bash
[riki@hoge]$ find ./ -type f | xargs grep CreateIten.php | awk -F: '{print $1}' | sort | uniq
./item/Item.php
./item/Item_1.php
./item/Item_10.php
./item/Item_11.php
./item/Item_12.php
./item/Item_13.php
./item/Item_14.php
./item/Item_15.php
./item/Item_16.php
./item/Item_17.php
./item/Item_18.php
./item/Item_19.php
./item/Item_2.php
./item/Item_20.php
./item/Item_3.php
./item/Item_4.php
./item/Item_5.php
./item/Item_6.php
./item/Item_7.php
./item/Item_8.php
./item/Item_9.php
```

うん。よさ気。
パイプで渡してるものを一応解説しておきます。

コマンド|意味
---|---
awk -F: | 受け取った結果の区切り文字を`:`にする
`{print $1}' | `:`で区切った結果の1列目を出力
sort | ファイルでソートをかける
uniq | ユニークを取る

#### じゃあ、一旦ここでスクリプトファイルを作っておく

`replace.sh`という名前のスクリプトを作って、実行権限を与えておきます。

```bash
[riki@hoge]$ touch replace.sh
[riki@hoge]$ chmod +x replace.sh 
```

一旦、さっきコマンドをスクリプトに書いて、ちゃんとファイルが出力されるか見てみよう。

```bash:replace.sh
#/bin/sh

find ./ -type f | xargs grep CreateIten.php | awk -F: '{print $1}' | sort | uniq
```

```bash
[riki@hoge]$ ./replace.sh 
./item/Item.php
./item/Item_1.php
./item/Item_10.php
./item/Item_11.php
./item/Item_12.php
./item/Item_13.php
./item/Item_14.php
./item/Item_15.php
./item/Item_16.php
./item/Item_17.php
./item/Item_18.php
./item/Item_19.php
./item/Item_2.php
./item/Item_20.php
./item/Item_3.php
./item/Item_4.php
./item/Item_5.php
./item/Item_6.php
./item/Item_7.php
./item/Item_8.php
./item/Item_9.php
./replace.sh
```

あぁ。自分自身のファイルが出てきてしまった。
そうですよね。スクリプトの中にgrep書いてるので、それが引っかかっているのです。
じゃあ、無視するようにしておきましょう。

```bash:replace.sh
#/bin/sh

find ./ -type f | xargs grep CreateIten.php | grep -v replace.sh | awk -F: '{print $1}' | sort | uniq

```

3個目のパイプで`grep -v replace.sh`と書いて、自分自身を無視するようにしました。

```bash
[riki@hoge]$ ./replace.sh 
./item/Item.php
./item/Item_1.php
./item/Item_10.php
./item/Item_11.php
./item/Item_12.php
./item/Item_13.php
./item/Item_14.php
./item/Item_15.php
./item/Item_16.php
./item/Item_17.php
./item/Item_18.php
./item/Item_19.php
./item/Item_2.php
./item/Item_20.php
./item/Item_3.php
./item/Item_4.php
./item/Item_5.php
./item/Item_6.php
./item/Item_7.php
./item/Item_8.php
./item/Item_9.php
```

よーしいい感じ。

### じゃあ、これらのファイルに置換をかけていく

更に、少し汎用性を持たせる為に、置換前文字列を第一引数に、
置換後の文字列を第2引数に渡せるような仕様にしてみました。

```bash:replace.sh
#/bin/sh

COMMAND=`basename $0`

if [ $# -ne 2 ] ; then
    echo "usage ${COMMAND} [before] [after]"
    exit;
fi

BEFORE=$1
AFTER=$2

for file in `find ./ -type f | xargs grep CreateIten.php | grep -v replace.sh | awk -F: '{print $1}' | sort | uniq`
do
  echo "replacing $file before:$BEFORE afiter:$AFTER"
  cp $file $file"_tmp"
  sed -e "s/${BEFORE}/${AFTER}/" $file"_tmp" > $file
  rm $file"_tmp"
done

```

#### 実践！

```bash
[riki@hoge]$ ./replace.sh CreateIten.php CreateItem.php
replacing ./item/Item.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_1.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_10.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_11.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_12.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_13.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_14.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_15.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_16.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_17.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_18.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_19.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_2.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_20.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_3.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_4.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_5.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_6.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_7.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_8.php before:CreateIten.php afiter:CreateItem.php
replacing ./item/Item_9.php before:CreateIten.php afiter:CreateItem.php
```

うまくいってそう！
よし、じゃあ実際に置換できているのか確かめてみましょう。

```bash
# 置換前の文字列はもうなくなってる
[riki@hoge]$ find ./ -type f | xargs grep CreateIten.php | grep -v replace.sh 

# 置換後のやつになってるかなー
[riki@hoge]$ find ./ -type f | xargs grep CreateItem.php | grep -v replace.sh 
./item/Item_13.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_4.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_9.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_19.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_20.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_18.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_14.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_6.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_12.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_11.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_10.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_17.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_7.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_1.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_2.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_16.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_3.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_8.php:require_once __DIR__.'/../CreateItem.php';
./item/Item_15.php:require_once __DIR__.'/../CreateItem.php';
./item/Item.php:require_once __DIR__.'/../CreateItem.php';
./item/Item.php:CreateItem.php
./item/Item_5.php:require_once __DIR__.'/../CreateItem.php';
```

出来たー！
使い勝手あまりよくなさそうですが、一応gitに上げておきます。

https://github.com/ritukiii/replace_sh