TL;DR: 以下を実行すれば、サブディレクトリ以下の*.cppと*.hppにBOMつけられるよ
- 変換したいファイルを含むディレクトリを普通にエクスプローラーで開く
- Shift+右クリック
- コマンドウインドウをここで開く
-
powershell
と打ってPowerShellを起動 - 以下の1行をコピペ入力
get-childitem * -include *.cpp,*.hpp -Recurse | foreach-object {((&{if ((Compare-Object (get-content $_.FullName -encoding byte)[0..2] @(0xEF, 0xBB, 0xBF)).length -eq 0){ @() } else { ([byte[]] @(0xEF, 0xBB, 0xBF)) } }) + (get-content $_.FullName -encoding byte)) | set-content $_.FullName -encoding byte}
背景
日頃からVisualStudioを愛してやまないみなさま、こんにちは。
VisualStudioといえば、C++コンパイラがなぜかUTF8のファイルにBOMを要求することで悪名高いですが、ついに2017が出た昨今、昔話となりました。
めでたしめでたし。
と、思いました?
実はVS2017ってExpress版がどうなるか未定で、少なくとも本記事執筆時点ではないんですよね。
ということはどうなるかというと、当然、
- BOMがついていない
- 日本語混じり
の両方の条件を満たしたファイルをビルドしようとすると死にます。
つらい。
ということで、2017年度も始まったというのにもかかわらず、今日も今日とてVisualStudioユーザーの一部はBOMをつけなければならないのです。
既存の手法
BOMをつける方法、いくつかあります。王道は
- Linuxを実機でもVMでも持ってきて、そこでnkfとか使う
- bash on windowsにnkfとかを入れて使う
- windows用のunix2dosを使う
- 専用のなんかそういうツールを使う
あたりですね。このうちどれかが使えるならそれでもいいです。しかしながら、以下のような欠点があり、あまり賢くないです
- Windows以外が必要
- ネットワークへの接続環境が必要(※bash on windowsはセキュリティーソフトの対応が不十分なせいでaptできないことがある)
- 改行コードまで変更してしまう
これらを解決する手法が、今回の手法です
提案する手法
冒頭に書いたとおり
- 変換したいファイルを含むディレクトリを普通にエクスプローラーで開く
- Shift+右クリック
- コマンドウインドウをここで開く
-
powershell
と打ってPowerShellを起動 - 以下の1行をコピペ入力
get-childitem * -include *.cpp -Recurse | foreach-object {((&{if ((Compare-Object (get-content $_.FullName -encoding byte)[0..2] @(0xEF, 0xBB, 0xBF)).length -eq 0){ @() } else { ([byte[]] @(0xEF, 0xBB, 0xBF)) } }) + (get-content $_.FullName -encoding byte)) | set-content $_.FullName -encoding byte}
です。この手法であれば
- Windowsだけで完結できる
- なにかをダウンロードしなくてよい(※最低限この記事が読めていれば目コピーする)
- なにかをインストールしなくてよい
- レジストリとか設定いじったりしなくていい
- 改行コードはそのまま
ということで、 多分これが一番早いと思います汎用で手軽だと思います 。
詳細
手順の4までで、検索先のディレクトリをカレントディレクトリにしてPowerShellが起動しています。
で、5はPowerShellの中身です。手軽にワンライナーでコピペできるようにしていますが、ちゃんと改行などを入れると以下のようになります。
get-childitem * -include *.cpp,*.hpp -Recurse
| foreach-object
{
((&{
if ((Compare-Object (get-content $_.FullName -encoding byte)[0..2] @(0xEF, 0xBB, 0xBF)).length -eq 0)
{
@()
}
else
{
([byte[]] @(0xEF, 0xBB, 0xBF))
}
})
+ (get-content $_.FullName -encoding byte))
| set-content $_.FullName -encoding byte
}
順に説明しますが、まず、0行目のget-childitem * -include *.cpp,*.hpp -Recurse
で、カレントディレクトリ(*)以下の*.cppと*.hppなファイルを再帰的に検索します。
そしてそれを、パイプラインで2行目のforeach-object
に渡していますので、見つかった*.cppや*.hppの各ファイル(ファイルの絶対パスが$_.FullName
に格納)に対して、2行目以降が実行されます。
3-12行目は、スクリプトブロックです。この中身(4-11行目)で評価された最後の値が返り値になります。
その返り値は、@()
という空の配列か、([byte[]] @(0xEF, 0xBB, 0xBF))
という3バイトの配列です。この3バイトの配列がUTF8のBOMに相当します。
どちらを返すかは、4行目で判断しています。先頭3バイトを見て、BOMと同じであれば、既にBOMありなので空の配列、そうでなければBOMということになります。
で、13行目でそのBOM(か空の配列)の後ろにファイルの中身自体をくっつけてます。
これで中身ができたので、14行目でファイルを書き出します。めでたしめでたし。
まとめ
ということで、よくやる割に毎回再発明している気がするので自分のメモがてらまとめました。
「もっとこっちのほうが良いよ!」みたいなマサカリあればコメント等、大歓迎です。