はじめに
この投稿はシェル初心者によるものです。
なので説明が不十分なところがあるかもしれません(誤りがあれば指摘お願いします
)。
きっかけ
GitBashはGitコマンドやcdくらいしか使っていなかったのですが、ネットワークの調子が悪くなったので、試しにgoogle.co.jpにpingをうってみました。
$ 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に設定済みです
。
文字コードの変更の仕方:
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にすれば文字化けは起こらなくなりますが、それはやりたくないので
。"git bash 文字化け ping"でググったところGit Bashでのpingの文字化けを解決という直球なタイトルの記事が引っかかり、その記事で紹介されている以下のシェルスクリプトを.bashrcファイルにコピペし、Git Bashを再起動したところpingの結果が文字化けしないようになりました。
function wincmd() {
CMD=$1
shift
$CMD $* 2>&1 | iconv -f cp932 -t utf-8
}
alias ping='wincmd ping'
$ ping google.co.jp
google.co.jp [172.217.31.131]に ping を送信しています 32 バイトのデータ:
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
172.217.31.131 の ping 統計:
パケット数: 送信 = 4、受信 = 0、損失 = 4 (100% の損失)、
(タイムアウトしてますが)めでたし、めでたし……![]()
.bashrcファイルの場所:
デフォルトだとC:\Users\<ユーザ名>にあります。
謎のシェルスクリプトを紐解く
文字化けはされなくなりましたけど、コピペした内容がさっぱりで気持ち悪いので自身の勉強がてら解説いたします。わかりやすくするため、先頭に行番号を付けました。
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つずれます。
標準出力
ここが一番の難所でした
。
4行目には|がありますが、これは左の処理の出力を右の処理に渡すという演算子なので、$CMD $* 2>&1とiconv -f cp932 -t utf-8の2つの処理からなっていることがわかります。
$CMDには、2行目の処理によりpingが格納されていて、$*は今の引数全体を表しているので、google.co.jpを指します。
shiftで1つずらしたのは、コマンド名と引数を分けるためだったんですね。
で、その後の2>&1ですが……
>はおなじみの比較演算子で、&はコマンドをバックグラウンドで実行する演算子です。意味が分からず、途方に暮れているとteratailで2>&1はどういう意味?という質問を見つけました。回答によると、これはリダイレクトという機能で、標準エラー出力を標準出力に出力するという高頻出な書き方だそうです。
よって、$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のコマンドを実行するケースはありそうなのですが、皆さんはどのように回避しているのでしょう
?
- ↑のような設定を各
Windowsコマンドでやっている -
Windowsコマンドはコマンドプロンプトで実行している -
Git Bashの文字コードをShift_JISにしている - そもそも
Windowsを使っていない
のどれかだと思うんですけど![]()
補足
この記事を書き終わってから、知人にexplainshellというサイトを教えてもらいました。このサイトのテキストフィールドにシェルコマンドを入力すると、各コマンドや演算子ごとに詳しく説明してくれます。英語が読める方にはお勧めです
。