■文字コードのプログラムへの入力
キーバインドとは、キーボードのキーと「文字」(≒おおむねASCII文字)の対応表。
キーバインドの種類は何種類かある。日本語キーボードの場合は主にjp106。
(Linuxでは/etc/sysconfig/keyboardファイルに「KEYTABLE=jp106」のように指定したら、tty実端末とpts仮想端末の両方で上記のキーバインドが適用される)
日本語キーボードと言っても、すべての日本語をキーに印字できないので、jp106キーボードを選んでも、「キーの押下≒キーにバインドされたASCII文字などの指定」に過ぎず、バインドされた文字をカーネルがメモリ上のキーバッファに書きむ。プログラムはキーバッファから文字を取り出して、データとして使うマルチバイト文字コードに変換する。
※キーバッファには単純にキーボードから入力されたキーに対応する文字の並びを記録してるだけで、「echo ぷにぷに」のようなコマンド部分の「echo」を構成する「e」は「c」と「ぷ」や「に」をローマ字入力した際の「p」や「u」などの文字の並びを記録してる。
(マルチバイト文字、外字、「プログラム固有の絵文字」などをプログラムが使う場合にはキーバッファから文字を取り出したプログラムが変換プログラムでソフトウェア的に変換して自分が使う文字コードにする)
shift-jisやUTF-8のような標準化されたマルチバイト文字コードの場合は、標準化された文字コード表を実装したライブラリによって符号化され、外字やプログラム固有絵文字などはプログラム側のルールで符号化する。
OSやエディタなどのプログラムはマルチバイト文字(の文字コード)をキーボードと変換プログラムを経て取得する。
「A」キーを押下したときに、「A」のASCII文字コードはキーバッファに書き込まれ、キーバッファから取り出した「A」を「あ」のUTF-8文字コードもしくはshift-jis文字コードに変換し別のところにバッファするプログラムが存在する。
tty実端末やpts仮想端末からマルチバイトコード文字の入力処理をするプログラムおよび設定を「日本語環境」というようだ。(自信ない)
centos6の場合、コンソール接続されたtty実端末からのマルチバイトコード文字入力をデフォルトでは処理しない。
(jp106にキーバインドするだけなら前述のファイル編集でできるようになるが、「半角/全角」などのキーで日本語を入力できるように設定できないwww)
これを処理できるようにする方法を調べたけどわからなかった。tty直接端末から日本語入力する必要性自体不明だがとりあえずtty実端末からの日本語入力に関して華麗にスルーすることを決め込む。キリッ。
pts仮想端末に接続されて起動するbashへの文字コード入力環境は下記のように行える。
■gnome-terminalからpts仮想端末が接続されて起動するbash
日本語環境をインストールする必要がある。
例えばgnomeからpts仮想端末が接続されて起動したbashの場合では、下記のように「Japanese Support」というrpmパッケージグループをインストールする。
# yum groupinstall "Japanese Support"
インストール後、システムリブートして起動したgnome-terminal上では「半角/全角」キーで、日本語入力を切り替えられるようになる。
入力される文字コードの種類は、gnome-terminalの「端末」メニュー→「文字コードの設定」で、UTF-8とかshift-jisを変更できるようになる。
■sshdからpts仮想端末を接続されて起動するbash
特に何もインストールする必要なし。
PC側のsshクライアントソフトの送信文字コードを指定してやれば、クライアントPCで生成したマルチバイト文字コード列をその文字コードに変換して送信する。受信するsshdサーバは送信されてきた文字コードが日本語だろうがスワヒリ語だろうがその文字コードをそのまま受信してbashやbashから実行されたコマンドなどのプログラムに渡す。プログラム側は受け取ったhoge文字コードの文字を(たとえ文字化けしても)そのまんま処理する。
具体例として、teratermの送受信文字コードは下記のように設定する。
teratermの「設定」メニュー→「端末」クリックで現れる「端末の設定」画面上の、「漢字-受信」と「漢字-送信」のプルダウンメニューで、UTF-8とかshift-jisを選択する。
以上のようにlinuxのpts仮想端末は、クライアント側で「送信」と「受信」の文字コードを指定して、bashやbashからforkしたプログラムに入出力する文字コードを決定する。
gnome-terminalやteratermのようなターミナルソフトではgnome-terminalやteratermの設定で入出力する文字コードを指定する。
プログラムがファイルや他のプログラムや出力装置に出力する文字コードはプログラム次第。それを受け取った側が変換すれば文字化けせず、変換しなければ文字化けする場合があるってだけ。
プログラムがディスプレイやプリンタに文字コードを出力する場合、出力される文字は、文字コードと紐づけられたフォントや装飾情報で出力されるドット列がつくられフレームバッファに書き込まれる。あるいはポストスクリプトインタプリタなどのページ記述プログラムに渡され印刷イメージやPDFファイルが作られる。
【検証1】 echoコマンドに入力する文字コードとechoコマンドから出力する文字コード
①teraterm
ダ> ps -ef
・・・省略・・・
root 1812 1 0 17:49 ? 00:00:00 /usr/sbin/sshd
・・・省略・・・
root 3106 1812 0 17:55 ? 00:00:00 sshd: root@pts/0
root 3108 3106 0 17:55 pts/0 00:00:00 -bash
・・・省略・・・
ダ> locale -a |grep ja
ja_JP
ja_JP.eucjp
ja_JP.sjis ←sjisロケールある
ja_JP.ujis
ja_JP.utf8
japanese
japanese.euc
・teratermの送信文字コード=UTF-8、受信文字コード=UTF-8に設定
ダ> locale
LANG=ja_JP.UTF-8 ←UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
ダ> date
2017年 9月 15日 金曜日 19:20:24 JST ←$LANGとteratermの受信文字コードが等しいので文字化けしない。 ちなみにteratermの受信文字コードをsjisにしたら化けた
ダ> echo あいう |hexdump -C
00000000 e3 81 82 e3 81 84 e3 81 86 0a |..........| ←UTF-8の「あいう」
0000000a
ダ> echo あいう | tee a
あいう
ダ> cat a|hexdump -C
00000000 e3 81 82 e3 81 84 e3 81 86 0a |..........| ←UTF-8
0000000a
ダ> file a
a: UTF-8 Unicode text ←UTF-8
・teratermへの送信文字コード=shift-jis、受信文字コード=shift-jis
繝> locale ←プロンプトが文字化けた。PS1に入力した値がUTF-8だからbashもこれをただUTF-8で出力する。
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
繝> date
2017蟷エ 9譛 15域律 驥第屆譌・ 19:08:48 JST ←dateコマンドの出力も文字化けた。dateコマンドはutf-8で出力している。
繝> cat a
縺ゅ>縺 ←teratermへの送信文字コードをUTF-8にしてファイルに書き込んだファイルの文字コードはUTF-8
※catコマンドはUTF-8データをUTF-8でただ出力する。ターミナルの受信文字コードがUTF-8でなければこのように文字化けする。ターミナルの受信文字コードはプログラムの出力ストリームをPC側文字コードにデコードする文字コードの指定
繝> echo あいう |hexdump -C
00000000 82 a0 82 a2 82 a4 0a |.......| ←shift-jisですね
00000007
繝> echo あいう | tee b
あいう
繝> cat b
あいう ←sjisデータをcatした出力はsjisでteratermはこれをsjisと見なして送信してるので結果文字化けしてない
繝> cat b|hexdump -C
00000000 82 a0 82 a2 82 a4 0a |.......| ←shift-jis
00000007
繝> file b
b: Non-ISO extended-ASCII text ←shift-jis
繝> LANG=ja_JP.sjis
繝> locale
LANG=ja_JP.sjis ←shift-jis
LC_CTYPE="ja_JP.sjis"
LC_NUMERIC="ja_JP.sjis"
LC_TIME="ja_JP.sjis"
LC_COLLATE="ja_JP.sjis"
LC_MONETARY="ja_JP.sjis"
LC_MESSAGES="ja_JP.sjis"
LC_PAPER="ja_JP.sjis"
LC_NAME="ja_JP.sjis"
LC_ADDRESS="ja_JP.sjis"
LC_TELEPHONE="ja_JP.sjis"
LC_MEASUREMENT="ja_JP.sjis"
LC_IDENTIFICATION="ja_JP.sjis"
LC_ALL=
繝> date
2017年 9月 15日 金曜日 19:14:12 JST ←文字化け治った
繝> cat a
縺ゅ>縺 ←teratermへの送信文字コードをUTF-8にしてファイルに書き込んだデータは文字化け
※LANG環境変数は、この環境変数を読み込んで出力文字コードを使い分けるプログラム(ここではdateコマンド)に文字コードを伝えるもの? 一方、ファイルなどのデータがそもそもある文字コード「hoge」で書かれているのをcatコマンドで標準出力させた場合、catコマンドは出力文字コードを使い分けせず、ありのままで標準出力する。(そんで文字化けする)
※ファイルやストリームなどからのテキスト入力を加工して出力するプログラムはLANG環境変数に関係なく、端末の設定にのみ依存する。一方、自分で出力するテキストを生成するプログラムはLANG環境変数(など)を読み込んで出力する文字コードを決定する?
Webサーバやcgiプログラムの場合は、コンテンツに実際に書かれている文字(コード)を、httpレスポンスとしてブラウザに出力する際に、指定した文字コードに変換する場合と、文字コードのエンコードをContent-typeヘッダで指定してデコードをクライアント側に委任する。
RDBMSの場合、テーブルに文字コード「hoge」で実際に書かれている文字(コード)を、SQLクエリの応答時に指定した文字コードに変換することができるが、それがOSの環境変数に変換する場合と、RDBMSのconfigで指定する場合、あるいはWebサーバのように、データを受け取るクライアントに委ねてエンコードしない場合などが考えられる?
繝> echo あいう |hexdump -C
00000000 82 a0 82 a2 82 a4 0a |...「.、.| ←shift-jis
00000007
繝> echo あいう | tee b
あいう
繝> cat b
あいう
繝> cat b|hexdump -C
00000000 82 a0 82 a2 82 a4 0a |...「.、.| ←shift-jis
00000007
繝> file b
b: Non-ISO extended-ASCII text ←shift-jis
繝> PS1="ダ> "
ダ> ←プロンプトの文字化け治った
・teratermへの送信文字コード=shift-jis、受信文字コード=UTF-8
_> cat a ←プロンプトがまた文字化けた
あいう ←UTF-8ファイルの文字化けが治った
_> cat b
△ ←sjisファイルが文字化けた
・teratermへの送信文字コード=UTF-8、受信文字コード=shift-jis
ダ> cat a ←プロンプトの文字化け治った
縺ゅ>縺 ←ファイルがまた文字化けに戻った
・_>
ダ> cat b
あいう ←sjisファイルの文字化けが治った
②gnome-terminal
ダ> ps -ef
・・・省略・・・
dagyah 5063 1 0 18:22 ? 00:00:00 /usr/bin/gnome-terminal -x /bin/sh -c cd '/home/dagyah/デスクトップ' && exec $SHELL
dagyah 5064 5063 0 18:22 ? 00:00:00 gnome-pty-helper
dagyah 5065 5063 0 18:22 pts/1 00:00:00 /bin/bash
・・・省略・・・
・gnome-terminalの文字コードをUTF-8に指定
ダ >locale
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
ダ >date
2017年 9月 15日 金曜日 19:32:22 JST
ダ >echo あいう |hexdump -C
00000000 e3 81 82 e3 81 84 e3 81 86 0a |..........|
0000000a
ダ >echo あいう |tee a
あいう
ダ >cat a | hexdump -C
00000000 e3 81 82 e3 81 84 e3 81 86 0a |..........|
0000000a
ダ >file a
a: UTF-8 Unicode text
・gnome-terminalの文字コードをshift-jisに変更
繝? >locale
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_PAPER="ja_JP.UTF-8"
LC_NAME="ja_JP.UTF-8"
LC_ADDRESS="ja_JP.UTF-8"
LC_TELEPHONE="ja_JP.UTF-8"
LC_MEASUREMENT="ja_JP.UTF-8"
LC_IDENTIFICATION="ja_JP.UTF-8"
LC_ALL=
繝? >date
2017蟷エ 9譛? 15譌・ 驥第屆譌・ 19:37:44 JST
繝? >echo あいう |hexdump -C
00000000 82 a0 82 a2 82 a4 0a |.......|
00000007
繝? >echo あいう |tee a
あいう
繝? >cat b | hexdump -C
00000000 82 a0 82 a2 82 a4 0a |.......|
00000007
繝? >file b
b: Non-ISO extended-ASCII text
繝? >LANG=ja_JP.sjis
繝? >locale
LANG=ja_JP.sjis
LC_CTYPE="ja_JP.sjis"
LC_NUMERIC="ja_JP.sjis"
LC_TIME="ja_JP.sjis"
LC_COLLATE="ja_JP.sjis"
LC_MONETARY="ja_JP.sjis"
LC_MESSAGES="ja_JP.sjis"
LC_PAPER="ja_JP.sjis"
LC_NAME="ja_JP.sjis"
LC_ADDRESS="ja_JP.sjis"
LC_TELEPHONE="ja_JP.sjis"
LC_MEASUREMENT="ja_JP.sjis"
LC_IDENTIFICATION="ja_JP.sjis"
LC_ALL=
繝? >date
2017年 9月 15日 金曜日 19:39:34 JST
繝? >echo あいう |hexdump -C
00000000 82 a0 82 a2 82 a4 0a |...「.、.|
00000007
繝? >echo あいう |tee b
あいう
繝? >cat b |hexdump -C
00000000 82 a0 82 a2 82 a4 0a |...「.、.|
00000007
繝? >file b
b: Non-ISO extended-ASCII text
繝? >PS1="ダ> "
ダ>
※teratermのときと同じ結果ですね
■システムの言語環境とは?
あるシステムの言語環境が「UTF-8」とか、「shift-jis」とか、「EUC」とかってよく言われるのはいったい何のことをさすのだろう?
上記で検証したように、サーバ側でpts仮想端末と接続したshellに接続するクライアントプログラム(teraterm、gnome-terminal、vnc-viewerなど)では、クライアント側から流し込む文字コードをクライアント側で一方的に決めて、クライアント側が受信した文字をデコードする文字コードをクライアント側が決める。
サーバ側でテキストデータを加工するだけのプログラムソフトは文字コードお構いなしでテキストデータをプログラムソフトのアルゴリズムで処理だけしてそのまま出力する。出力される文字コードが何なのかを知ってデコードするのはクライアント側に委ねられている。
一方、サーバ側でテキストデータを生成するプログラム(dateなど)では、configや環境変数で生成データの文字コードをサーバ側で決める。
蓄えられてるテキストデータの文字コードは唯一無二だが、どの文字コードなのかは、データの生成起点(プログラミング、インポート、キーボード入力など)からデータが蓄えられるまでの途中で行われるエンコードとデコードによって決まる。
俺が考えるに、システムの言語環境とは、OSに限って言えば下記の3つではないか
・定番プログラム類が生成するテキストの文字コードを決定している環境変数のこと
・ターミナルから受信する文字コードとしてターミナルに設定を期待する文字コード
・ターミナルへ送信する文字コードとしてターミナルに設定を期待する文字コード
さらにミドルウェアやサーバアプリケーションまで「環境」に含めると下記の3つが追加されるのではないか
・システムに入力されるテキストデータとして期待する文字コード
・システムが蓄えるテキストデータの文字コード
・システムが出力するテキストデータの文字コード
※プログラムに入力されるASCIIコード文字は、単なるデータの場合と、コマンドを構成する一部分の場合があるが、マルチバイトコードはほぼ単なるデータ。
※WindowsPCにつながってるキーボードからの日本語テキスト入力をwindowsはshift-jisにしてプログラムに渡す。 もちろん、Windowsにインストールされたterminalソフトやブラウザやエディタがファイルやネットワークにテキストを出力する際に、他の文字コードにエンコードするように設定できる。
※windowsPC上で起動するアプリケーションが表示する文字列をwindowsクリップボードにコピーしてwindows上で起動するターミナルソフトにペーストした場合に、ターミナルソフトと接続する別システムへ送信される文字コードは、先述のようにターミナルソフトの送信文字コードで指定されたものになる。
例えば、windowsPC上で起動するメモ帳やexcelが表示する文字列はwindows内のデータとしてはshift-jisで保存されていて、クリップボードにコピーされた状態でもshift-jisで、ターミナルソフトにペーストするときにターミナルソフトで送信コードとして設定した文字コードにエンコードされる。
また、windowsPC上で起動するFFFTPやwin-scpなどのファイル転送アプリケーションでは、テキストモードでファイルを送受信する場合には、エンコードを手動指定するか自動判別でエンコードされる。 バイナリモードで送受信する場合には、ありのままのビット配列でデータが送受信される。
ぶっちゃけ、蓄えるデータの文字コード、出力する文字コード、あるいは入力されるときにエンコードするルールは、そのプログラムの作り手によってどうにでも作ることができるのでプログラム次第だ。そんでOS側には環境変数という歴史的な機能があり、これを読み込むプログラムもある。fileコマンドのように目印もないのに文字コードを推測するプログラムも存在する。入出力書き込み文字コードは設定で選択できる仕様のものや、おせっかいなプログラムは自動で処理してくれるものもある。入出データや書き込むデータの文字コードをどう処理するか、何の処理もしないのか、環境変数を参照するのか、推測するのか等いろいろな可能性があって、プログラム次第としか言いようがない。
賢すぎて透過的に処理してくれるプログラムはありがたいが、ノイズを含むファイルやストリームで思わぬ誤作動をする可能性がある。catやechoのようにシンプルに「何もしない」プログラムの方がありがたい場合もあると思う。