Git Bash for Windows でpingの結果が文字化けするのを直した話

#### はじめに
この投稿はシェル初心者によるものです。
なので説明が不十分なところがあるかもしれません（誤りがあれば指摘お願いします:bow_tone1:）。

#### きっかけ
`GitBash`は`Git`コマンドや`cd`くらいしか使っていなかったのですが、ネットワークの調子が悪くなったので、試しに`google.co.jp`に`ping`をうってみました。

```text
$ ping google.co.jp

google.co.jp [172.217.31.163]▒▒ ping ▒𑗐M▒▒▒Ă▒▒܂▒ 32 ▒o▒C▒g▒̃f▒[▒^:
▒v▒▒▒▒▒^▒C▒▒▒A▒E▒g▒▒▒܂▒▒▒▒B
▒v▒▒▒▒▒^▒C▒▒▒A▒E▒g▒▒▒܂▒▒▒▒B
▒v▒▒▒▒▒^▒C▒▒▒A▒E▒g▒▒▒܂▒▒▒▒B
▒v▒▒▒▒▒^▒C▒▒▒A▒E▒g▒▒▒܂▒▒▒▒B

172.217.31.163 ▒▒ ping ▒▒▒v:
    ▒p▒P▒b▒g▒▒: ▒▒▒M = 4▒A▒▒M = 0▒A▒▒▒▒ = 4 (100% ▒̑▒▒▒)▒A
```

ちなみに、文字コードはUTF-8に設定済みです:sweat:。

> 文字コードの変更の仕方：
`Windows`の場合、`Git Bash`の画面の適当なところで右クリック→`Options`でオプション画面を開き、左のメニューから`Text`を選択すると`Locale`と`Character set`のプルダウンメニューが表示されるので適切な値に変更します。たぶん、`ja_JP`と`UTF-8`でいいと思います。

#### なぜ文字化けするのか?
`ping`や`ipconfig`などの`Windows`のコマンドは`Shift_JIS`(`CP932`)で出力されるようになっています。なので、`Git Bash`で文字コードを`UTF-8`にすると、`Shift_JIS`で出力されたものを`UTF-8`で表示しようとするため文字化けが起こります。

#### あっさり解決
`Git Bash`の文字コードを`Shift_JIS`にすれば文字化けは起こらなくなりますが、それはやりたくないので:sweat_smile:。"git bash 文字化け ping"でググったところ[Git Bashでのpingの文字化けを解決](http://naktatty64.hatenablog.com/entry/2016/02/23/002508)という直球なタイトルの記事が引っかかり、その記事で紹介されている以下のシェルスクリプトを`.bashrc`ファイルにコピペし、`Git Bash`を再起動したところ`ping`の結果が文字化けしないようになりました。

```shell:.bashrc
function wincmd() {
    CMD=$1
    shift
    $CMD $* 2>&1 | iconv -f cp932 -t utf-8
}
alias ping='wincmd ping'
```

```text
$ ping google.co.jp

google.co.jp [172.217.31.131]に ping を送信しています 32 バイトのデータ:
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。

172.217.31.131 の ping 統計:
    パケット数: 送信 = 4、受信 = 0、損失 = 4 (100% の損失)、

```
（タイムアウトしてますが）めでたし、めでたし……:angel:

> .bashrcファイルの場所：
デフォルトだと`C:\Users\<ユーザ名>`にあります。

#### 謎のシェルスクリプトを紐解く
文字化けはされなくなりましたけど、コピペした内容がさっぱりで気持ち悪いので自身の勉強がてら解説いたします。わかりやすくするため、先頭に行番号を付けました。

```shell:.bashrc
1 function wincmd() {
2    CMD=$1
3    shift
4    $CMD $* 2>&1 | iconv -f cp932 -t utf-8
5 }
6 alias ping='wincmd ping'
```

##### エイリアスの設定
`function`は文字通り関数を意味していて、1~5行目は`wincmd`という名前の関数の処理を表しています。6行目の`alias`は`ping`というコマンド名を`wincmd ping`という名前に変更しています。

つまり、`ping google.co.jp`と入力してコマンドを実行すると`wincmd ping google.co.jp`に置き換えられ、関数`wincmd`が呼び出されることになります。

##### 引数を取得する
普段`Java`を書いているので`()`内が空にもかかわらず引数が渡ってくるというのが理解しにくい点なのですが、関数内で`$<num>`(numは1以上の数字)と書くと対応したインデックスの引数を取得することができます。引数はスペース区切りで複数渡すことができ、インデックスは先頭から割り振られるので、今回のケースでは、

`$1 = ping`
`$2 = google.co.jp`
となります。

> `$0`：
`$0`だとスクリプト名を取得できるようです。

なので、2行目では変数`CMD`に`ping`が代入されます。

##### 引数を操作する
3行目で唐突に`shift`という文字列が出てきますが、これは引数の配列のインデックスを1つ手前にずらします。今回のケースでは、`$2`の`google.co.jp`が`$1`に格納され、`$1`の`ping`は削除されます。よって、引数の状態は、

`$1 = google.co.jp`
となります。

> `shift`の引数：
ずらすインデックスの数は任意に設定することができ、`shift <num>`(numは1以上の数字)のように書きます。省略すると`shift 1`と解釈され、今回のように1つずれます。

##### 標準出力
ここが一番の難所でした:dizzy_face:。
4行目には`|`がありますが、これは左の処理の出力を右の処理に渡すという演算子なので、`$CMD $* 2>&1`と`iconv -f cp932 -t utf-8`の2つの処理からなっていることがわかります。

`$CMD`には、2行目の処理により`ping`が格納されていて、`$*`は**今**の引数全体を表しているので、`google.co.jp`を指します。
`shift`で1つずらしたのは、コマンド名と引数を分けるためだったんですね。
で、その後の`2>&1`ですが……
`>`はおなじみの比較演算子で、`&`はコマンドをバックグラウンドで実行する演算子です。意味が分からず、途方に暮れているとteratailで[2>&1はどういう意味？](https://teratail.com/questions/1285)という質問を見つけました。回答によると、これはリダイレクトという機能で、標準エラー出力を標準出力に出力するという高頻出な書き方だそうです。
よって、`$CMD $* 2>&1`はコマンド `ping google.co.jp`の標準出力・標準エラー出力を右側の処理に渡すという意味になります。

##### 文字コード変換
`iconv`は`International Codeset Conversion Library`の略で文字コード変換をするコマンドです。使い方は`iconv -f <変換前の文字コード> -t <変換後の文字コード>`です。
なので4行目は、"`ping google.co.jp`の標準出力・標準エラー出力"を"Shift_JISからUTF-8に変換する"という意味になります。

#### まとめ
`Windows`のコマンドを実行する際に、エイリアスで独自定義した関数を呼び出し、その中でコマンドを実行し、出力結果を`UTF-8`に変換して返却しました。個人的には難解でしたがインターセプタっぽくて面白かったです（小並感）。
今回は`ping`を例に取り上げましたけど、エイリアスを追加すれば他の`Windows`コマンドにも対応できると思います。

#### 投稿に至った経緯
なんとなく実行した`ping`が文字化けして困ったので調べた内容をメモがてらまとめました。しかし、`Git Bash`で`Windows`のコマンドを実行するケースはありそうなのですが、皆さんはどのように回避しているのでしょう:rolling_eyes:?

1. ↑のような設定を各`Windows`コマンドでやっている
2. `Windows`コマンドはコマンドプロンプトで実行している
3. `Git Bash`の文字コードを`Shift_JIS`にしている
4. そもそも`Windows`を使っていない

のどれかだと思うんですけど:thinking:

#### 補足
この記事を書き終わってから、知人に[explainshell](https://explainshell.com/)というサイトを教えてもらいました。このサイトのテキストフィールドにシェルコマンドを入力すると、各コマンドや演算子ごとに詳しく説明してくれます。英語が読める方にはお勧めです:smiley:。

#### 参考ページ
- [Git Bashでのpingの文字化けを解決](http://naktatty64.hatenablog.com/entry/2016/02/23/002508)
- [シェルスクリプト入門 書き方のまとめ](http://motw.mods.jp/shellscript/tutorial.html)
- [2>&1はどういう意味？](https://teratail.com/questions/1285)
- [iconv(wikipedia)](https://ja.wikipedia.org/wiki/Iconv)
- [explainshell](https://explainshell.com/)