sedでディレクトリ配下のファイルにある「特定文字の直後にある大文字を小文字にする」方法

- 環境
    - OS : Windows10 64bit
    - シェル : GNU bash, version 4.4.19(2)-release (x86_64-pc-msys)
    - sed : GNU sed 4.4
    - find : GNU findutils 4.6.0
    - xargs : GNU findutils 4.6.0
    - grep : GNU grep 3.0

```bash:やり方
$ find {対象のディレクトリパス} -type f -name {対象ファイル名の一部} | xargs sed -i 's/\({特定文字}\)\({小文字にしたい文字}\)/\1\L\2/g'
```

# やりたいこと
## ファイルの中身を上書きで更新したい
```bash
# -iオプションで対象ファイルをそのまま編集する
$ sed -i 's/{置換前}/{置換後}/g'
```

## 対象のファイルはサブディレクトリも含めたい
```bash
# findでファイルを抽出して、xargsで一気に処理する
$ find {対象のディレクトリパス} -type f -name {対象ファイル名の一部} | xargs sed -i 's/{置換前}/{置換後}/g'
```
[参考：find + sed で一斉置換 - Qiita](https://qiita.com/somat/items/b775f7e082f1559707e2)

## 特定文字の直後にある文字を対象にしたい
### 「肯定的後読み」をやりたいが、 `sed` は対応していないらしい

|名前|正規表現|意味|
|:-:|:-:|:-:|
|肯定的先読み|foo(?=bar)|直後にbarがあるfoo(barは含まない)に一致|
|否定的先読み|foo(?!bar)|直後にbarがないfoo(barは含まない)に一致|
|肯定的後読み|(?<=bar)foo|直前に barがあるfoo(barは含まない)に一致|
|否定的後読み|(?<!bar)foo|直前にbarがないfoo(barは含まない)に一致|
[出展：正規表現の先読み・後読みを極める！ - あらびき日記](https://abicky.net/2010/05/30/135112/)

### なぜならば `sed` は `?` をメタ文字としていないのでできない
|grepコマンド|sedコマンド|awkコマンド|意味|
|:-:|:-:|:-:|:-:|
|`\( \)`|`\( \)`|`( )`|パターンのグループ化|
||`\1\2\3`|`\1\2\3`|後方参照|
|`\?`||`?`|直前の 1 文字あるいは 1 パターンの 0 回または 1 回だけ出現|

[出展：sed | テキストの置換処理を得意とするスクリプト言語](https://bi.biopapyrus.jp/os/linux/sed.html)

### だからグループ化を使うことにした
```bash
# 説明：「グループにしたい文字1」が「\1」に入って、「グループにしたい文字2」が「\2」に入っている。
$ sed 's/\({グループにしたい文字1}\)\(グループにしたい文字2\)/\1\2/g'

# 例：「ぽんすけ」と「チョビン」の間に「太郎」が入る。
$ echo 'I am ぽんすけチョビン' | sed 's/\(ぽんすけ\)\(チョビン\)/\1太郎\2/g'
I am ぽんすけ太郎チョビン
$ echo '私は、ぽんすけチョビンです。ぽんすけとお呼びください。' | sed 's/\(ぽんすけ\)\(チョビン\)/\1太郎\2/g'
私は、ぽんすけ太郎チョビンです。ぽんすけとお呼びください。
```
[参考：sedを使った部分置換 | ヘビィ・ＳＭＤ！](https://ameblo.jp/smd310/entry-10988072287.html)

## 小文字にしたい
```bash
# 小文字にする
$ sed 's/\({大文字}\)/\L\1/g'
# 例：
$ echo 'PONSUKE' | sed 's/\(SUKE\)/\L\1/g'
PONsuke
```

# ディレクトリ配下のファイルにある特定文字の直後にある大文字を小文字にする方法
```bash
$ find {対象のディレクトリパス} -type f -name {対象ファイル名の一部} | xargs sed -i 's/\({特定文字}\)\({小文字にしたい文字}\)/\1\L\2/g'
```

## やってみる
```bash:「[」の直後にある「ERROR」を小文字にしたい
# ディレクトリ構成
$ find Evernote/Logs/ -type f -name *.txt
Evernote/Logs/AppLog_2018-09-10.txt
Evernote/Logs/AppLog_2018-09-11.txt
Evernote/Logs/AppLog_2018-09-12.txt
Evernote/Logs/backup/AppLog_2018-09-03.txt
Evernote/Logs/backup/AppLog_2018-09-04.txt
Evernote/Logs/backup/AppLog_2018-09-05.txt
Evernote/Logs/backup/enclipper_2018-09-03.txt
Evernote/Logs/backup/enclipper_2018-09-04.txt
Evernote/Logs/backup/enclipper_2018-09-05.txt
Evernote/Logs/backup/enclipper_2018-09-06.txt
Evernote/Logs/enclipper_2018-09-10.txt
Evernote/Logs/enclipper_2018-09-11.txt
Evernote/Logs/enclipper_2018-09-12.txt
Evernote/Logs/enclipper_2018-09-13.txt

# 対象の文字を確認する：grepは「肯定後読み」できます。
$ grep -r -P '(?<=\[)ERROR' Evernote/Logs/
Evernote/Logs/AppLog_2018-09-10.txt:11:33:55 [ERROR  ] [14916] [2768] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-10.txt:11:33:55 [ERROR  ] [14916] [2768] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-10.txt:11:35:55 [ERROR  ] [14916] [14848] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-10.txt:12:29:55 [ERROR  ] [14916] [5468] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-10.txt:16:30:30 [ERROR  ] [8108] [10592] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-10.txt:16:30:30 [ERROR  ] [8108] [10592] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-11.txt:09:43:10 [ERROR  ] [12876] [11876] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-11.txt:09:43:10 [ERROR  ] [12876] [11876] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-11.txt:12:31:11 [ERROR  ] [12876] [1372] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-11.txt:13:15:11 [ERROR  ] [12876] [7064] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-12.txt:09:29:01 [ERROR  ] [8536] [12784] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-12.txt:09:29:01 [ERROR  ] [8536] [12784] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-12.txt:13:17:01 [ERROR  ] [8536] [7560] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-12.txt:13:57:01 [ERROR  ] [8536] [884] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:01:57 [ERROR  ] [10864] [8208] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:01:57 [ERROR  ] [10864] [8208] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:03:48 [ERROR  ] [10864] [7808] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:55:48 [ERROR  ] [10864] [12592] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-04.txt:09:34:57 [ERROR  ] [2844] [4888] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-04.txt:09:34:57 [ERROR  ] [2844] [4888] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:07:19 [ERROR  ] [16224] [1484] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:07:19 [ERROR  ] [16224] [1484] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:09:19 [ERROR  ] [16224] [8744] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:33:19 [ERROR  ] [16224] [11504] InternetErrorDlg return code: 0

# 「[」の直後にある「ERROR」を小文字にする
$ find Evernote/Logs/ -type f -name *.txt | xargs sed -i 's/\(\[\)\(ERROR\)/\1\L\2/g'

# 「[」の直後にある「ERROR」がなくなた
$ grep -r -P '(?<=\[)ERROR' Evernote/Logs/

# 「[」の直後にある「error」になった
$ grep -r -P '(?<=\[)error' Evernote/Logs/
Evernote/Logs/AppLog_2018-09-10.txt:11:33:55 [error  ] [14916] [2768] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-10.txt:11:33:55 [error  ] [14916] [2768] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-10.txt:11:35:55 [error  ] [14916] [14848] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-10.txt:12:29:55 [error  ] [14916] [5468] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-10.txt:16:30:30 [error  ] [8108] [10592] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-10.txt:16:30:30 [error  ] [8108] [10592] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-11.txt:09:43:10 [error  ] [12876] [11876] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-11.txt:09:43:10 [error  ] [12876] [11876] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-11.txt:12:31:11 [error  ] [12876] [1372] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-11.txt:13:15:11 [error  ] [12876] [7064] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-12.txt:09:29:01 [error  ] [8536] [12784] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/AppLog_2018-09-12.txt:09:29:01 [error  ] [8536] [12784] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/AppLog_2018-09-12.txt:13:17:01 [error  ] [8536] [7560] InternetErrorDlg return code: 0
Evernote/Logs/AppLog_2018-09-12.txt:13:57:01 [error  ] [8536] [884] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:01:57 [error  ] [10864] [8208] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:01:57 [error  ] [10864] [8208] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:03:48 [error  ] [10864] [7808] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-03.txt:11:55:48 [error  ] [10864] [12592] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-04.txt:09:34:57 [error  ] [2844] [4888] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-04.txt:09:34:57 [error  ] [2844] [4888] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:07:19 [error  ] [16224] [1484] 0% EDAMUserException: errorCode=DATA_CONFLICT parameter="OAuthCredential.accessToken.expired"
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:07:19 [error  ] [16224] [1484] Utility Store request has failed. Error: クライアント側で予期しない動作が発生したため、同期に失敗しました
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:09:19 [error  ] [16224] [8744] InternetErrorDlg return code: 0
Evernote/Logs/backup/AppLog_2018-09-05.txt:13:33:19 [error  ] [16224] [11504] InternetErrorDlg return code: 0

```