Hatena::ブログ(Diary)

ザリガニが見ていた...。 このページをアンテナに追加 RSSフィード

2015-02-09

ASCIIコードの秘密

本当はエスケープシーケンスのことを調べていたのだが、その前にASCIIコードについて調べることになってしまった...。文字コードの基本として知っているつもりだったASCIIコードについて、あらためて見直してみると、実は本当の意味をよく分かっていなかったことに気づいた。

ASCIIコード表

  • ASCIIコードは、7ビット(2進数7桁)の文字コードであり、全部で128のコードが定義されている。
  • 最も基本的な文字コードであり、その他多くの文字コードはこのASCIIコードと互換性を維持している。
00 10 20 30 40 50 60 70 
00NULDLESP0@P`p
01SOHDC1!1AQaq
02STXDC2"2BRbr
03ETXDC3#3CScs
04EOTDC4$4DTdt
05ENQNAK%5EUeu
06ACKSYN&6FVfv
07BELETB'7GWgw
08BSCAN(8HXhx
09HTEM)9IYiy
0ALFSUB *:JZjz
0BVTESC+;K[k{
0CFFFS,<L\l|
0DCRGS-=M]m}
0ESORS.>N^n~
0FSIUS/?O_oDEL
  • ASCIIコードはその性質から二つに分類できる。目に見える文字のコードと見えない文字のコード。
    • 見える文字のコードとは、い項目のコードである。(英数記号などの文字そのもの、印字可能文字)
    • 見えない文字のコードとは、オレンジの項目のコードである。(接続している機器を制御する役割がある)
      • SP(スペース)については、ASCIIコード制定当初は制御コードの扱いだったが、現在は見える文字(印字可能文字)の扱い。

制御コード

  • では、見えない文字のコードは何を意味するのだろうか?
  • 見えない文字のコードは、制御コードと呼ばれている。
  • それらの制御コードだけ抜き出して、その意味を調べてみた。
略語由来語句意味10進数コード16進数コード8進数コードcontrol
コード
\エスケープ
コード
NULNull文字無し0\x00\000^@\0
SOHStart Of Headingヘッダ開始1\x01\001^A
STXStart Of Textテキスト開始2\x02\002^B
ETXEnd Of Textテキスト終了3\x03\003^C
EOTEnd Of Transmission伝送終了4\x04\004^D
ENQEnquery問い合わせ5\x05\005^E
ACKAcknowledgement肯定応答6\x06\006^F
BELBell警告音を鳴らす7\x07\007^G\a
BSBack Space一文字後退8\x08\010^H\b
HTHorizontal Tabulation水平タブ9\x09\011^I\t
LFLine Feed / New Line改行10\x0A\012^J\n
VTVertical Tabulation垂直タブ11\x0B\013^K\v
FFForm Feed / New Page改ページ12\x0C\014^L\f
CRCarriage Return行頭復帰13\x0D\015^M\r
SOShift Outシフトアウト / ISO2022:GLへG1を呼び出す14\x0E\016^N
SIShift Inシフトイン / ISO2022:GLへG0を呼び出す15\x0F\017^O
DLEData Link Escapeデータリンク拡張(バイナリ通信開始)16\x10\020^P
DC1Device Control 1装置制御1(XON)17\x11\021^Q
DC2Device Control 2装置制御218\x12\022^R
DC3Device Control 3装置制御3(XOFF)19\x13\023^S
DC4Device Control 4装置制御420\x14\024^T
NAKNegative Acknowledgement否定応答21\x15\025^U
SYNSynchronous idle同期22\x16\026^V
ETBEnd of Transmission Block伝送ブロック終了23\x17\027^W
CANCancel取り消し24\x18\030^X
EMEnd of Medium記録媒体終端25\x19\031^Y
SUBSubstitute / End Of File文字置換 / ファイル終端26\x1A\032^Z
ESCEscapeエスケープシーケンス開始27\x1B\033^[\e
FSFile Separatorファイル区切り28\x1C\034^\
GSGroup Separatorグループ区切り29\x1D\035^]
RSRecord Separatorレコード区切り30\x1E\036^^
USUnit Separatorユニット区切り31\x1F\037^_
SPSpace空白文字32\x20\040
DELDelete削除マーカー127\x7F\0177^?
  • 制御コードの呼称は、便宜的に2文字か3文字の略語で表現されることが多い。
  • その由来となる語句から、なんとなくその意味は想像できるのだけど、
  • 実際にどんな制御しているのか真剣に考え始めると、ちゃんと理解できているものはほとんど無いことに気付く。

ASCIIコードが想定していた制御機器

  • ASCIIコードが制定されたのは1963年である。(ASA X3.4)
  • 当初は大文字しか登録されていなかったり、制御コードの体系も現在とは違っていた。

00 10 20 30 40 50 60 70 
00NULDC0SP0@P
01SOMDC1!1AQ
02EOADC2"2BR
03EOMDC3#3CS
04EOTDC4$4DT
05WRUERR%5EU
06RUSYN&6FV
07BELLEM'7GW
08FE0S0(8HX
09HTS1)9IY
0ALFS2 *:JZ
0BVTS3+;K[
0CFFS4,<L\ ACK
0DCRS5-=M]
0ESOS6.>N ESC
0FSIS7/?O DEL

よって、ASCII制御コードをちゃんと理解するには、テレタイプやタイプライターがどのように動作するのか想像すれば良い。

ASCII制御コードの意味

  • ASCII制御コードの詳細について、素晴らしい解説を見つけた。
  • 自分の知識ではこれ以上わかりやすい説明はできそうにないので、ここにそのまま引用させて頂くことにした。

以下は小林龍生、安岡孝一、戸村哲、三上喜貴編「bit別冊─インターネット時代の文字コード」(共立出版)から引用した。ただし、一部修正してある。

  • SPSpace(空白)

    空白を表す制御文字である。タイプライターやテレタイプでは空白を「打つ」のではなく、単に印字ヘッドを右に移動させるだけの動作であることから、制御文字として定義されている。なお、SPは単に空白を表しているだけではなく、単語と単語を隔てているものだとASCIIでは考えている。この点についてはFSとGSとRSとUSの項を参照されたい。

  • BSBack Space(1文字後退)

    SPの逆の動作を表す制御文字である。タイプライターやテレタイプでは印字ヘッドを左に移動させる制御文字であり、アクセント記号の合成やアンダーラインに用いられることが想定されていた。すなわち、タイプライターを打つときのように、アクセント記号を打ってからBSで1文字戻して、さらに必要な文字を打てばよいと考えられていたのである。アクセント記号の使い方として想定されていたものの例を図1に示す。

    f:id:zariganitosh:20150129160523p:image:w450

    図1.アクセント合成とアンダーライン

  • CRとLFCarriage Return(復帰)、Line Feed(改行)

    CRは、印字ヘッドを行の最初に戻すための制御文字である。LFは、紙を1行分送るための制御文字である。タイプライターやテレタイプにおいては、ピンチローラーを右端まで動かす(印字ヘッドが行の最初に来るようにする)動作と行送りの動作とが独立に行えるようになっているので、それぞれに制御文字が用意されているのである。

  • HTHorizontal Tabulation(水平タブ)

    印字ヘッドをタブマージンと呼ばれるところまで右に移動する制御文字である。タイプライターやテレタイプにおいては、通常8文字ごとにタブマージンが設定されていて、そこまで印字ヘッドを移動させるためのタブキーが準備されていた。このタブキーと同じ動作をさせるための制御文字である。

  • VTVertical Tabulation(垂直タブ)

    垂直タブマージンと呼ばれるところまで行送りを行う制御文字である。水平方向のHTに対応する垂直方向の移動を想定していたようだが、実際にはあまり使用されなかったようである。

  • FFForm Feed(改ページ)

    紙を1枚分送ってしまって、次の紙の最初の行に印字ヘッドを合わせる制御文字である。

  • BELBell(ベル)

    ベルを鳴らすための制御文字である。テレタイプなどのタイプライター型の端末では、端末の近くの人間を呼ぶ際に非常に重宝したようである。

  • SOとSIShift Out(シフトアウト)、Shift In(シフトイン)

    印字フォントの切り換えを意図して準備された制御文字である。すなわち、2つの印字ボールが搭載できるようなテレタイプにおいて、SOで特殊用途の印字ボールに切り換えて、SIで通常の印字ボールに戻すことを想定していたものである。これによって、たとえば強調文字への切り換えや、あるいはまったく別の記号印字ボールなどを使用できるように考えられていたものである。ただし、SOとSIの意味は、ISO 2022の登場によって変更されてしまった。

  • NULとDELNull(空文字)、Delete(削除)

    NULとDELは同一の意味を持つ制御文字である。これらの制御文字がデータ中に入っていても、データの内容には何の影響も与えず、無視してよいということになっている。では、このような無視してよい制御文字が2種類もあることにどのような意味があるのだろうか。紙テープ上のデータを例に考えてみよう。

    f:id:zariganitosh:20150129160633p:image:w450

    図2.紙テープ上の「This is pen.」

    図2は「This is pen.」という文字列を紙テープ上に開けたものである。2進数で左から順に11010100、11101000、01101001、11110011、10100000、01101001、11110011、10100000、11110000、01100101、11101110、00101110と並んでおり、一番上の偶数パリティビットを除けば16進数で54、68、69、73、20、69、73、20、70、65、6E、2Eと並んでいることがわかる。ところが、この紙テープ上に記されているデータはこれだけではない。紙テープという媒体(25.4mm幅のテープに最初から繰り出し孔が開けられている)の性質上、上記のデータの前後に16進数で00がずらっと並んでいるように見えるのである。すなわち、この紙テープをテープリーダーで読み込ませると、データの前後に00が読み込まれてしまうことになるのである。そこで、ASCIIでは00を無視してよい制御文字NULと定義しているのである。これによって、紙テープ上にいくらNULがあったとしても、データとしては「This is pen.」の部分だけが有効となるので、特に問題にはならないわけである。

    f:id:zariganitosh:20150129160632p:image:w450

    図3.紙テープ上の「This is a pen.」

    次に、図2の紙テープ上の「This is pen.」というデータを「This is a pen.」に書き換えることを考えてみよう。図3が書き換えた結果である。すなわち、元の紙テープの「pen.」の部分の孔を全て開けてしまって、その後に「a pen.」というデータを追加したわけである。孔を全て開けた部分には、実際には16進数で7Fが並んでいることになる。ここで、図3の紙テープが「This is a pen.」というデータだとみなされるためには、孔を全て開けた文字、すなわち7Fを、NULと同様に無視してよいことにしなければならない。そこで、ASCIIでは7Fも無視してよい制御文字DELと定義しているのである。

    NULとDELの両方を無視してよい制御文字としている理由は、実は他にもある。紙テープのように各ビットの0→1の操作が不可逆である媒体においては、データは最初全てNULで埋まっていて、誤ったデータの削除にはDELが用いられる。ところが、もし1→0の操作が不可逆であるような媒体があったなら、そのような媒体においてはデータは最初全てDELで埋まっていて、誤ったデータの削除にはNULが用いられるだろう。そこまでを考慮した上で、ASCIIではNULとDELの両方を無視してよい制御文字としているのである。

  • EMEnd of Medium(媒体終端)

    紙テープなどの媒体の終端を表すための制御文字である。EMを用いることで、テープリーダーなどのデバイスに媒体の終わりを知らせることができ、テープの交換要求などが出せることになるのである。なお、EMは本当の媒体の終端に入っている必要はなく、実際、紙テープにおいてはEMの後にNULが続くのはやむを得ない。

  • SOHとSTXとETXとEOTStart of Heading(ヘッダ開始)、Start of Text(テキスト開始)、End of Text(テキスト終了)、End of Transmission(伝送終了)

    伝送路上の通信を制御するための制御文字である。SOHは通信の開始を意味し、EOTは通信の終了を意味する。通信中には1つあるいはそれ以上のテキストが含まれており、各テキストの最初にはSTXを、最後にはETXを用いる。通信中のテキスト以外の部分にはテキストに付帯するデータを送ることが可能になっており、特にSOHから最初のSTXまでの間のデータはヘッディング、最後のETXからEOTまでの間のデータはトレーラーと呼ばれる。

  • ETBEnd of Transmission Block(ブロック転送終了)

    伝送路の都合で、1つの通信を複数に分割しなければならなくなった場合に、各分割の末尾を表す制御文字である。

  • ACKとNAKAcknowledgement(肯定応答)、Negative Acknowledgement(否定応答)

    送られてきた通信データに対し、正しく受け取ることができたかどうかを答えるための制御文字である。正しく受け取れた場合はACKを、どうも正しく受け取れなかった場合にはNAKを答えることになっている。

  • ENQEnquiry(問い合わせ)

    伝送路が接続されたときに、最初に送る「あんた誰?」を表すための制御文字である。

  • SYNSynchronous Idle(同期信号)

    伝送路がシリアル伝送路であるときに、伝送信号を同期させるための制御文字である。0010110というビットパターンを持つことから、ストップビット数やパリティの有無にかかわらず、正しく同期が取れるようになっている。

  • CANCancel(キャンセル)

    それまでに送ったデータをキャンセルするための制御文字である。どこまで遡ってキャンセルするかは、通信の当事者同士で事前に決めておかなければいけない。

  • SUBSubstitute Character(置換)

    送られてきた文字に誤りがあることが発見された場合、その文字の代わりにメモリなどを埋めておくための制御文字である。実際にはテープリーダーがパリティエラーを発見した際などに、エラーのあった文字の代わりにSUBを送る、というような使われ方をしたようである。

  • FSとGSとRSとUSFile Separator(ファイルセパレータ)、Group Separator(グループセパレータ)、Record Separator(レコードセパレータ)、Unit Separator(ユニットセパレータ)

    ASCIIでは、単語の集まりがユニットであり、ユニットの集まりがレコードであり、レコードの集まりがグループであり、グループの集まりがファイルであり、ファイルの集まりがテキストである、という情報階層が想定されていた。この階層において、FSはファイルの区切りを表す制御文字であり、GSはグループの区切りを表す制御文字であり、RSはレコードの区切りを表す制御文字であり、USはユニットの区切りを表す制御文字であり、SPは単語の区切りを表す制御文字である。なお、これらの制御文字は1C〜20に連続して配置されている。

  • DC1〜DC4Device Control 1(装置制御1、XON)、Device Control 2(装置制御2)、Device Control 3(装置制御3、XOFF)、Device Control 4(装置制御4)

    補助装置の起動・停止を行うための制御文字である。DC1とDC2は補助装置の起動を、DC3とDC4は補助装置の停止を行うものとされていたが、詳細は通信の当事者同士で事前に決めておかなければいけない。

  • ESCとDLEEscape(エスケープ)、Data Link Escape(データリンク拡張)

    ESCは制御機能の追加に用いる制御文字である。ESCに続く何文字かで、新たな制御機能を表すことになっている。DLEは、ESCと同じだが、主に伝送機能の追加に用いることになっていた。ただし、ESCの実際の使用方法は、ISO 2022の登場によって細かく規定されてしまった。

ページ移転のお知らせ

上記引用を元に、理解したこととか、想像したことなど...

印字位置のコントロール

  • タイプライターはローラーに巻き付けた紙に対して活字を打ち付けて印字する。
  • 活字が打刻される位置は常に同じ。固定されている。
  • 紙が巻き付けられたローラーを上下に回転、左右に移動することで印字する位置を調節しているのだ。
    • 紙が巻き付けられたローラー = キャリッジとか、プラテンと呼ばれている。
    • より正確には、ローラー単体をプラテン、ローラーを含む左右に可動するユニット全体をキャリッジと呼ぶらしい。
  • よって、印字位置を調整する制御コードは、キャリッジの位置を制御しているのだ。
  • 印刷の最初はキャリッジを右端にセットしておく。(紙の印字位置としては左端にセットされることになる)
  • 一文字打刻するごとに、キャリッジは1文字分左に移動する。(紙の印字位置としては1文字右に移動することになる)
  • 制御文字が入力されると、キャリッジは以下のように制御される。
略語由来語句意味10進数
コード
16進数
コード
8進数
コード
control
コード
\エスケープ
コード
SPSpace空白文字 (キャリッジを1文字分左へ移動させる)32\x20\040
BSBack Space一文字後退(キャリッジを1文字分右へ移動させる)8\x08\010^H\b
CRCarriage Return行頭復帰 (キャリッジを右端に戻す)13\x0D\015^M\r
LFLine Feed / New Line改行   (キャリッジを1行分上方向へ回転させる)10\x0A\012^J\n
FFForm Feed / New Page改ページ (キャリッジを回転させて、1ページすべて送る)12\x0C\014^L\f
HTHorizontal Tabulation水平タブ (キャリッジを行の途中に設定したタブ位置まで移動させる)9\x09\011^I\t
VTVertical Tabulation垂直タブ (キャリッジを設定した行数上方向へ回転させる)11\x0B\013^K\v
  • キャリッジの動きに注目してみると、制御コード本来の意味が見えてきた!
  • SpaceやBack Spaceはキャリッジを1文字分左右に移動するだけ。
    • 何も印字していない部分を移動すれば、そこにスペースが生まれる。
    • 既に印字してある部分を移動するなら、それはカーソル移動キーのような動きとなる。
  • Line FeedやForm Feedしても必要な行数が送られるだけであり、キャリッジの左右の位置は変化しない。
  • 次の行頭に移動させるには、上記に加えてCarriage Returnしておく必要があったのだ。*1
  • タイプライターは1文字ずつ印字して、一旦印字した文字は削除できないという物理的制約から上記のような制御を行うしかなかったのである。

  • ところが、ASCIIコードを採用したテレタイプがコンピューターに接続されるようになって事情が変わってきた。
  • コンピューターに接続されたテレタイプで入力した文字は、コンピューターのメモリに保存されるようになったのだ。
    • また、テレタイプ自身も入力した文字をバッファメモリに一旦保持するようになった。
  • メモリは紙と違って、書き換え・挿入・削除が自由自在に何度でもできる。
  • すると"helloworld"と書いてしまっても、helloとworldの間でスペースキーを押して"hello world"に修正可能な仕様が当然となる。
  • 今までキャリッジを移動する制御コードの意味しかなかったSpaceが、空白" "という視覚的な間隔を生み出す文字となったのだ!

  • 同様に、もはやキャリッジの存在しないメモリ上でCarriage Returnする必要もなくなった。
  • 仮にメモリ上で旧来のタイプライターのLine Feedに相当する動作をさせるなら、
  • 行末でLine Feedした瞬間に、次の行をNullまたはSpaceで満たす必要が生じてしまう...。
    • メモリは1次元配列に情報を保持する記録メディア。
    • 最初から2次元のテキスト領域を持つ紙と違って、何も書き込まれなければテキストを保存する領域も存在しない。
    • Line Feedして前行と同じ位置をキープするならば、行頭からカーソル位置までに何らかの情報が必要になるのだ。
    • その情報として、何もないという意味のNullか、視覚的な間隔であるSpaceを使うのが妥当と思われる。
  • Line Feedの動作を忠実に再現しようとすると、メモリ領域も浪費するし、無駄なメモリアクセスも増やしてしまうのだ。

それはとても不合理なことに思えてくる。

  • そこでUNIX系のOSでは、Line FeedにCarriage Returnの役割も含めてしまった。
    • Line Feed = New Lineと解釈して、カーソルを次の行の先頭に移動する制御コードとしてしまった。
    • メモリ領域で改行するということは、新しく1行追加するという意味合いが強い。だからNew Line。
  • 一方、Windows系のOSでは、Carriage Return・Line Feedの順に並んでいる時のみ改行する仕様となった。
    • Line Feed単独の時は、何もしない。

  • UNIXにおいて、Line FeedはNew Lineになってしまったが、Form Feedは従来の動作を再現しようとしているように思える。
  • たとえば、ターミナル.appでForm Feedを使うと、次の行に移動するが、前の行のカーソル位置を保持している。
    • 本来、Form Feedは用紙を1ページ分進める制御コードなのだが、
    • ターミナル.appにはページ概念のない連続したスクロールエリアしかないので、1行進める仕様なのかもしれない。
$ echo -e "hello\fworld"
hello
     world
  • echoコマンド自体はForm Feedの文字コードを出力しているのだけど、
$ echo -e "hello\fworld" | xxd
0000000: 6865 6c6c 6f0c 776f 726c 640a            hello.world.
  • 画面に出力されたhello worldをコピーして、文字コードを確認してみると、
hello
     world
$ pbpaste | xxd
0000000: 6865 6c6c 6f0a 2020 2020 2077 6f72 6c64  hello.     world
0000010: 0a                                       .

それは改行とスペースの文字コードに変換されている!

  • Form Feedをどのように扱うかはアプリケーションによって変わってくるかもしれないが、
  • 少なくともターミナル.appにおいては、1個のLFと5個のSPに変換されているのだ。

区切り文字

  • 元々SPはキャリッジを1文字分左へ移動させる制御コードなのだが、
  • 同時に、単語と単語を区別する区切り文字としての役割もあった。
  • ASCIIコードにおいて、単語は情報の最小単位であり、
    • 単語が集まってUnitという単位となり、
    • Unitが集まってRecordという単位となり、
    • Recordが集まってGroupという単位となり、
    • Groupが集まってFileという単位となる。
    • データベースにおけるフィールド・レコード・テーブル・ファイルの関係に似ている。
  • 上記の階層情報を区切る制御コードが、10進数コードで28〜32まで順に並んで登録されているのだ。
略語由来語句意味10進数コード16進数コード8進数コードcontrol
コード
\エスケープ
コード
FSFile Separatorファイル区切り28\x1C\034^\
GSGroup Separatorグループ区切り29\x1D\035^]
RSRecord Separatorレコード区切り30\x1E\036^^
USUnit Separatorユニット区切り31\x1F\037^_
SPSpace空白文字32\x20\040

DELの真実

  • NUL(\x00)は現在でもよく使う概念である。何もない・存在しないことを意味する。数学的な0と似ているので理解しやすい。
  • 一方、DEL(\x7F)は、検索してみると「1文字削除・削除・抹消」などの説明がヒットする。
  • ある意味どれも正しいのだけど、当時の削除がどのようなものだったか知っておかないと、誤解を生む。

DELには、現在のdeleteキーの働きはないのだ!

  • 自分も誤解していた。てっきり、deleteキーは文字コード\x7Fを生成して、文字を削除しているものだと思っていた。
  • しかし、試してみると分かるが、\x7Fは何も削除しない。
  • 以下のように入力すれば、前後の1文字どちらかが消えることを期待してしまうが、実は何も消えない...。
$ echo -e "hello\x7fworld"
helloworld
  • echoコマンド自体は\x7Fの文字コードを出力しているのだけど、
$ echo -e "hello\x7fworld" | xxd
0000000: 6865 6c6c 6f7f 776f 726c 640a            hello.world.
  • 画面に出力されたhelloworldをコピーして、文字コードを確認してみると、
helloworld
$ pbpaste | xxd
0000000: 6865 6c6c 6f77 6f72 6c64 0a              helloworld.

そこには\x7Fのコードは存在しない!


  • 実は、これこそが\x7F DELの働きである。
  • \x7Fという文字コードは何も存在しないと解釈されるのだ!
  • 当時、テレタイプには紙テープリーダー/ライターが付属していた。
  • 紙テープに穴を開けて、穴のある部分は1、ない部分は0と解釈して、データを読み書きするのだ。
  • テレタイプで受信したデータを文字に印刷しただけでは、その文字データは再利用できなくなってしまうが、
  • 紙テープにも記録しておけば、後で印刷もできるし、別な場所にも転送できる。だから紙テープは必須である。
  • ところが、通信中の文字化けや誤字脱字を見つけてしまった場合、どうするべきか?
  • 物理的に穴を開けてしまう紙テープを修正することはできない...。
  • 特に、長い長いデータの最後の方で間違いを見つけてしまった場合、もう一度すべてを紙テープに出力し直すのは、切ない思いになる...。

そこで\x7Fの出番である!

  • 7ビットのASCIIコード\x7Fをビット列で表現すると 1111111 となる。
  • 7ビットすべてが1の状態。つまり、紙テープではすべて穴の開いた状態となる。
  • \x7Fは何も存在しないと解釈されるので、例えば「This is pen.」を「This is a pen.」に修正したい場合は、
  • 以下のように穴を開ければ、すべてを書き換えなくても修正できるのだ。

f:id:zariganitosh:20150129160633p:image:w450

f:id:zariganitosh:20150129160632p:image:w450

ページ移転のお知らせ

つまり、DELには日常で使う二重線による取り消しのような意味がある!

  • よって、DEL = 削除マーカーと表現した方が分かりやすいと思った。
  • 一方、現在のdeleteキーは文字コードそのものを取り除く削除である。

  • 以上の仕組みを知ると、DEL一つだけ別のコード領域にある理由も理解できる。
    • 他の制御コードはすべて\x00〜\x20の連続した領域*2にある。
  • 削除マーカーとしてすべてのビット位置に穴を開ける必要があったので、物理的な理由から\x7Fにするしかなかったのだ。

controlコード

  • キーボードには、実際に見える文字のキーが並んでいる。
  • 例えば、aというキーを押せば、aの文字が画面に出力される。
  • では、見えない文字の制御コードはどのように入力したら良いのだろうか?
  • それは、controlキーを押しながら、見える文字@ a〜z [ \ ] ^ _ ?のどれかを押すのだ。
    • 例:control @は、NUL \x00の文字コードを生成する。
    • 例:control aは、SOH \x01の文字コードを生成する。
  • どのような仕組みになっているかというと、controlキーには、同時に押されたキーの文字コードの最上位7ビット目を反転する機能があるのだ。
@ = \x40 = 100 0000 最上位7ビット目を反転すると... 000 0000 = \x00 = NUL
A = \x41 = 100 0001 最上位7ビット目を反転すると... 000 0001 = \x01 = SOH
B = \x42 = 100 0010 最上位7ビット目を反転すると... 000 0010 = \x02 = STX
...中略...
_ = \x5F = 101 1111 最上位7ビット目を反転すると... 001 1111 = \x1F = US
? = \x3F = 011 1111 最上位7ビット目を反転すると... 111 1111 = \x7F = DEL
  • controlには「制御」という意味がある。
  • 制御コード、つまりcontrol codeを生成する役割のキーだからcontrolキーと呼ばれているのだ。
  • 昔からcontrolキーが何をコントロールするのか疑問だったのだが、その疑問がようやく解けた!
  • ASCIIコード表を見直してみると、以下のような対応になっている。
  • 制御コードから右に4マス移動する(\x40を加算する)と、controlキーを併用する文字を見つけられる。
  • 右端のDELの場合は、最初の列に戻って右に4マス移動すればいい。

00 10 20 30 40 50 60 70 
00NULDLESP0@P`p
01SOHDC1!1AQaq
02STXDC2"2BRbr
03ETXDC3#3CScs
04EOTDC4$4DTdt
05ENQNAK%5EUeu
06ACKSYN&6FVfv
07BELETB'7GWgw
08BSCAN(8HXhx
09HTEM)9IYiy
0ALFSUB *:JZjz
0BVTESC+;K[k{
0CFFFS,<L\l|
0DCRGS-=M]m}
0ESORS.>N^n~
0FSIUS/?O_oDEL

実際の入力方法
  • ターミナル.appにおいて、上記controlキーを併用して制御コードを入力するには、control-Vの操作に続け行う。
  • 例えば、control-GでBELを入力したい時は、control-V control-Gとキー操作して入力するのだ。
$ echo -e "^G"
  • control-V control-Gは、制御コード\x07をダイレクトに入力する。
  • "^G"は、文字イメージのない制御コード\x07を表現する記号である。(^はcontrolキーを意味するようだ)
  • control-V control-Gで入力した"^G"の場合は、文字コードは\x07となる。
# control-V control-Gと入力した場合
$ echo -ne "^G" | xxd
0000000: 07                                       .
  • 目に見える文字"^"と"G"の場合は、それぞれの文字コード2文字分となる。
# ^とGを入力した場合
$ echo -ne "^G" | xxd
0000000: 5e47                                     ^G
  • まったく同じコマンドに見えても、入力方法によっては全然意味が違ってしまうのだ。気を付けよう!
    • control-V control-Gで画面に表示された^Gをでコピー・ペースト(command-C・command-V)したとしても、^とGの2文字を入力した扱いになってしまう...。
    • 但し、キル・ヤンク(control-K・control-Y)の場合は、制御コード1文字の扱いとなった。
control-Vの力
  • control-Vには、直後入力された文字を純粋なASCIIコードとして解釈させる効果があるようだ。
  • control-V無しでは、コマンドの入力途中でcontrol-Gした瞬間にBEL(ベル音・警告音)が鳴ってしまう。
  • control-V control-Gとすることで、カーソル位置に\x07の制御コードが入力され、BELは鳴らない。
  • また、bashでコマンド入力中のcontrol-A・control-Eには、カーソルを行頭・行末に移動する、というASCII制御コードとは別の機能が割り当てられている。
  • control-Vによって、そのようなコマンド入力中の別の機能を発現させずに、純粋なASCII制御コードのみを入力できる。
  • 入力された制御コードは、ターミナル.appの画面上でcontrolコード、あるいはescキーのcontrolコード^[で始まるエスケープシーケンスで表現されるのだ。
  • 例:
    • control-V、delete(バック デリート)してみると、制御コード DELの\x7Fが入力された。
      • ターミナル >> 環境設定 >> プロファイル >> 詳細 >> 入力:DeleteキーでControl+Hを送信チェックなしの設定の場合
$ echo -en ^? | xxd
0000000: 7f                                       .
    • control-V、fn-delete(フォワード デリート)してみると、エスケープシーケンス\x1B 5B 33 7Eが入力された。
$ echo -en ^3~ | xxd
0000000: 1b5b 337e                                .[3~

\によるエスケープコード

  • controlキーを使えばすべての制御コードは入力できるのだけど、
  • よく使う制御コードは、さらに\によるエスケープコードも用意されている。

略語由来語句意味10進数コード16進数コード8進数コードcontrol
コード
\エスケープ
コード
NULNull文字無し0\x00\000^@\0
BELBell警告音を鳴らす7\x07\007^G\a
BSBack Space一文字後退8\x08\010^H\b
HTHorizontal Tabulation水平タブ9\x09\011^I\t
LFLine Feed / New Line改行10\x0A\012^J\n
VTVertical Tabulation垂直タブ11\x0B\013^K\v
FFForm Feed / New Page改ページ12\x0C\014^L\f
CRCarriage Return行頭復帰13\x0D\015^M\r
ESCEscapeエスケープシーケンス開始27\x1B\033^[\e

ASCII制御コードは相当古くなってしまった...。

  • ASCIIコード制定当時の環境(テレタイプ・コンピューター・紙テープ・プリンターなどの利用環境)では必要な手続きや制御だったかもしれないが、
  • 現在の利用環境では、実は多くの制御コードがほとんど使われなくなってしまったと思っている。
  • 覚える価値のある制御コードは上記9コードだけで十分なのかもしれない。(それ以外使った記憶がない)
  • 例えばタブを入力したい場合は、以下のようにエスケープコード\tをそのまま書き込むだけでいい。
$ echo -e "abc\tefg"
abc	efg
  • 上記のような\エスケープを使った書き方は、現在も多くの環境でサポートされている。
  • controlコードよりも書きやすく覚えやすいので、利用できる時は\エスケープをよく使う。
    • 但し、コマンドによってはcontrolコードでないと解釈してくれない場合もある。
8進数と16進数の指定方法
  • また、8進数や16進数で制御コードを直接指定する場合も\エスケープを利用することになる。
    • 例:8進数でタブを指定。
$ echo -e "abc\011def"
abc	def
    • 例:16進数でタブを指定。
$ echo -e "abc\x09def"
abc	def
\の仕様の違いに注意
  • \エスケープの書き方は、利用するシェルの仕様によって決まる。
  • また、実行するコマンドが\をどのように解釈するかによっても動作は違ってくる。
  • \エスケープを誰が解釈しているのか?常に意識しておくことが大事。(シェルが解釈しているのか?コマンドが解釈しているのか?)
    • 順序としては、まずシェルが\エスケープを解釈する。
    • その後コマンド引数に\エスケープが残る場合は、コマンドが解釈する。
  • 例:
    • bashの$'XXXX'フォーマットの8進数表現は、\nnnである。
    • 一方、bashの内部コマンドecho -eの8進数表現は、\0nnnである。
# \177は、制御コードDELと解釈される
$ echo $'abc\177def'
abcdef
    • 同じ\177がecho -eでは単なる文字列と解釈されてしまう。
# \177は、4つの文字コードと解釈される
$ echo -e 'abc\177def'
abc\177def
    • echo -eの8進数表現は\0で始まる必要がある。
# \0177は、制御コードDELと解釈される
$ echo -e 'abc\0177def'
abcdef
    • ちなみに$'\0177'と書いてしまうと、\に続く3桁まで制御コード、4桁目は単なる数字と解釈される。
# $'\0177'は、制御コード\017(\x0F)と数字の7と解釈される
$ echo $'abc\0177def'
abc7def

$ echo $'abc\0177def' | xxd
0000000: 6162 630f 3764 6566 0a                   abc.7def.
  • その違いを知らずに\エスケープすると、思い通りの動作をしてくれず、悩む。
  • そして文字として目に見えないので、深い悩みにとなってハマることが多い...。
    • 自分がよくハマってる。

XONとXOFF

  • Device Controlの1〜4は、接続された機器(デバイス)の起動・停止を制御する。
  • 事前に当事者間でどのような制御を行うか取り決めが必要らしいが、
  • 一般的によく使われたのが、XON(DC1)・XOFF(DC3)という制御でだった。
  • 文字データ通信において、受信側の処理が遅く、一時的に文字データを保存しておくバッファが一杯になってしまうことがある。
    • 受信側のバッファから溢れた文字は失われてしまうので、送信側にXOFF(DC3)の制御コードを送る。
      • XOFFを受けた送信側は、文字データの送信を一時的に停止する。
    • 受信側の処理が捗りバッファの空きに余裕が出たら、送信側にXON(DC1)の制御コードを送る。
      • XONを受けた送信側は、文字データの送信を再び開始する。
  • 以上のような通信制御が、かつて行われていたのだ。

  • そして、その名残は現在のターミナル.appにも残る。
  • 例えば、1秒ごとに日時を出力する(10回繰り返す)以下のコマンドを実行中に...
$ for i in `seq 1 10`; do date; sleep 1; done
  • control-Sを押すと、画面出力を一時停止する。
    • 出力が停止しても、コマンドの処理は途切れることなく進んでいる。
  • control-Qを押すと、停止していた画面出力を再開する。
    • 最近のターミナル.appはcontrol-Qに限らず、何らかのキーを押すと再開するかも。
  • control-Sは、XOFFのcontrolコードであり、
  • control-Qは、XONのcontrolコードなのだ。
  • 果たして、ターミナル.appのこの制御が何の役に立つか分からないが、
  • ASCIIコードのXON・XOFFに由来する機能であることは想像できる。

\x1A = ファイル末尾の真相

  • ASCII制御コードのSUB \x1Aは本来、Substitute (代理・置換)という意味であった。
  • 紙テープリーダーや通信データからパリティエラーを検出して、間違った文字データと思われる場合、
  • とりあえず、その文字コードをSUB \x1Aに置き換えて保存しておくような運用が行われていたようだ。
  • ところで、かつてCP/Mという8ビットCPU時代のOSが、ファイル末尾を\x1Aで埋めていた。
  • CP/Mはファイルを128バイトのブロック単位でしか管理できなかったため、
  • ブロック中の未使用領域は、\x1Aというコードで埋める仕様となっていた。
  • 128の倍数でないサイズのファイルは、ファイル末尾に\x1Aを書き込むことになる。
    • 確率的には、127/128の確率で末尾が\x1Aとなるはず。
  • つまりCP/Mは、ほとんどすべての場合ファイル末尾が\x1Aになっていたことになる。
  • その後、CP/M用のソフトウェアがMS-DOSに移植されるようになった。
  • 本来MS-DOSはファイルを1バイト単位で管理できるので、ファイル末尾に\x1Aは不要なはずであった。
  • ところが、移植されたCP/Mアプリのファイル末尾のほとんどすべてが\x1Aとなっていたために、
  • \x1A = EOF(End Of File)= ファイル末尾を表現する文字コードと誤解されてしまったらしい。
      • CP/M用アプリのEOF判定ロジックで\x1Aを利用していた可能性もある。DOS用に移植する時もそのロジックはそのまま使われた可能性大。
      • また、移植されたDOS用アプリで作ったファイルをCP/M側で読み込む需要もあったはず。そのようにして\x1A = EOFの呪縛から逃れられなくなったのかもしれない。
  • 現在、テキストファイルの末尾に\x1Aが必要なOS環境は存在しない!

\x1A = ファイル末尾は、CP/M時代の古き遺産なのである。

現在も覚えておくべき終端の扱い
  • #include <stdio.h>を宣言しているC言語環境においては、文字列の末尾はNull \x00であることが求められている。

  • UNIX系のOSでは、ターミナル.appでcontrol-Dを押すと、ログアウトしてしまう。*3
  • control-Dは、ASCII制御コードのEOT(End of Transmission)\x04に由来していると思われる。
  • 意図しないログアウトを防止したい場合は、例えば IGNOREEOF=2 のように設定しておくと良い。
    • 連続2回のcontrol-Dまで警告を出力してくれる。(指定した数値の回数だけ警告される)
    • 連続3回のcontrol-Dを押した時だけ、ログアウトを実行する。
$ IGNOREEOF=2
$ Use "logout" to leave the shell. # control-D 1回目
$ Use "logout" to leave the shell. # control-D 2回目
$ logout                           # control-D 3回目

[プロセスが完了しました]
  • IGNOREEOF=2を.bashrcとか.bash_profileに書いておくと常に警告してくれる。


参考ページ

  • 以下のページがたいへん参考になりました!
  • すべてのページに深ーく感謝です!

タイプライター・テレタイプについて


ASCIIの誕生とその時代背景など


ASCIIコードと日本の文字コードについて


テレタイプが接続されていた通信方式について

*1:ちなみに、テレタイプによる文字通信では、Carriage ReturnしてからLine Feedする、というのがお決まりの手順のようだ。当時、キャリッジ(あるいはプリンタヘッド)を移動させるにはある程度の時間がかかったので、先にCarriage Returnを開始させて、その最中にLine Feedも行っていたようだ。さらに時間を稼ぐために、CR・LF・NULとか、CR・CR・LFなどの改行コードを送信することもあったらしい。

*2:但し8ビット目の桁あふれを無視すれば、\x7F + 1 = \x00であり、\x00 - 1 = \x7Fなので、\x7Fも連続した領域と考えることもできる。

*3:logoutするのは、ログインシェルの場合。別のシェルを起動していたり、別のユーザーでログインしている場合はexitする。

kabakenkabaken 2015/02/09 18:29 35年以上ASCIIコードに触れてきましたが、知らないことばかり。
大変勉強になりました。ありがとうございました!

sgo2sgo2 2015/02/10 02:26 うろ覚えでアテにならないタレコミですが、DOSだといくつかはcopyコマンドが関係してます。
CRLF→copy hoge.txt PRN のようにテキストファイルをcopyで印刷する事ができるのですが、この時ファイルの内容をポートに直接流し込めるからだと何かで読みました。
CTRL-Z→copy CON hoge.txt でテキストファイルを作る事ができるのですが、昔のDOSではCTRL-Zで入力終了となっていました。CP/Mとの互換性を保ちつつ最低限の実装で済ませるため0x1a=EOFとしていた可能性があります。

zariganitoshzariganitosh 2015/02/10 09:46 kabakenさん、
自分も今回ASCIIについて知らないことばかり、その驚きがこの記事になってます。その背景を知ると面白いですよね!

sgo2さん、
なるほど、copyコマンドで直接プリンタデバイスにデータを送信する目的はありそうですね。そのプリンタが昔ながらのキャリッジ移動方式ならば、CRとLFの両方が必要になりますよね。
CTRL-Z=0x1A=終端は、やはりCP/Mとの互換性に由来しそうですよね。参考ページのリンク先でも推測されていますが、CP/M用アプリケーションのEOFを判定するロジックでも0x1Aを利用していたのではないかと言及されています。DOSに移植するときにも、そのままそのロジックが使われたのではないかと。また過渡期には、移植されたDOS用アプリケーションのファイルをCP/M側で読み込む需要もあったハズですよね。

noocytenoocyte 2015/02/11 12:19 > ターミナル.appのデフォルトではcontrol-Sは画面出力を一時停止するが、使い道が謎。

テレタイプの次の時代 (おもに1980年代) に,ビデオ表示端末 (VDT) というものが使われました.
代表的な機種は VT-100 で,80文字×24行程度のテキストを CRT に表示する表示部とキーボード
からなり,コンピュータとはシリアル回線 (RS-232C) で接続されていました.

現在画面に表示されている以上のテキストは記憶していないので,バックスクロールはできません.
数十行以上のテキストファイルを cat で出力すると,すぐに画面から流れ去ってしまいます.
そこで流れを止めたい時に ^S を押し,再開したい時に ^Q を押していたのです.

それでも,通信速度と表示速度が速くなると ^S をちょうどいいタイミングで押すのが困難になります.
ちょっとでも遅れたら最初からやり直し.

これでは困るので,1ページ (24行) 出力したら自動的に一時停止するフィルタ more (less の前身)
が開発されました.

現在の「ターミナルエミュレータ」は,このような端末をエミュレート (+機能拡張) しているから
そういう名前なのです.(昔の UNIX なら /etc/termcap に色々な端末の設定データが入っていました.
最近は terminfo らしいですが.)

zariganitoshzariganitosh 2015/02/12 12:06 なるほど!
^S・^Q ---> more ---> less という歴史があったのですね。
ターミナル.appにはスクロールもあるし、必要ならlessの様なページャーもあるのに、画面出力を一時停止する意図はどこにあるのか疑問でしたが、謎が解けました。
自分はVDTを触ったことがないので、頂いたコメントでバックスクロールできない当時の端末事情がとてもよく分かりました。

また、今回この記事を書くにあたりnoocyteさんの「文字コードに関する覚書と実験」もたいへん興味深く読ませていただきました。ありがとうございました!

asakawayaasakawaya 2015/02/13 16:08 CP/MのEOFの件ですが
DOSの場合は、ディレクトリにファイルサイズが書かれてるのですが、CP/Mではファイルサイズではなく、使用してるブロックサイズしかわかりません。
そのため、テキストファイルの行末をEOFで表現する必要があったのです。
DOSにはCP/Mからの移植が多かったので、プログラムでEOFを見る必要が初期の頃はありました。
テキストエディタもEOFを付加するのがほとんどでした。
固定長のIBMディスクフォーマット(DOSじゃないよ)に、可変のテキストを格納した場合もエンドを示すコードEOT(End of Text)が必要でした。
外部のマシンに可変長のデータを送る場合も最後を教えてやる必要がありますからその場合もEOT(EOF)が必要です。

zariganitoshzariganitosh 2015/02/15 13:42 asakawaya さん、

当時を知る体験に基づいたコメント、ありがとうございます。
自分にはCP/Mの経験が全くないので、当時を実感する貴重な手がかりとなります。

自分が初めて手に入れたパソコンはFM-new7でした。
プログラムはデータレコーダを使ってロード・セーブしていました。(今思えば紙テープではなかったのが残念)
当時はOSなんて概念はまったく理解できていなくて、
自分にとってはF-BASICの編集モードがOSのようなものでした。

OSを知らない代わりに、ちょっと凝ったことをしようと思うとハードウェアにダイレクトアクセスするしかありませんでした。
ところが、この経験によってパソコンというものがどういう仕組みで動いているか、身をもって実感することができました。
データレコーダのIO領域を使って音声合成しようとしてみたり、
ハンドアセンブルしたマシン語をビデオ領域に送り込んで画面を高速描画しようとしてみたり、
失敗してマシン語が暴走してばかりでしたが、CPUがハードウェアをコントロールする基礎知識となりました。

OSの概念を知ったのはずっと後で、就職して仕事で会社のMS-DOSパソコンを触るようになってからです。
そういえばFM-new7を買う時に、拡張スロットにZ80カードというものを刺せば、CP/Mのソフトウェアも動かせると、店員さんが話していたことを思い出しました。
大人になってようやく、その意味が理解できたのでした。CP/MというのはOSだったのか!と。

いわもと こういちいわもと こういち 2015/05/19 20:54 全体を通してシェル/ttyドライバ/端末の切り分けが出来ていないのでおかしな主張になっている部分が見受けられます。
例えば改行の話ですが、

シェルやコマンド等が LF を出力

ttyドライバが LF を CR+LF に変換

端末が CR と LF をそれぞれ解釈して表示

という事が行われています。
試しに stty -onlcr を実行して tty での LF → CR+LF 変換を止めてやると、
端末の LF のみの時の動作がわかると思います。

改行時には端末には今も昔も CR と LF が送られてきていますので、
メモリ効率等の端末の事情と Unix での改行の扱いを結びつけるのは無理があると思います。

zariganitoshzariganitosh 2015/05/21 11:06 おっしゃるとおり、シェル/ttyドライバ/端末の切り分けをあまり考えずに書いてます。
体感するために stty -onlcr を実行してみました。
試しに以下のコマンドを実行してみて、LF本来の動作を見てみます。

$ echo -e "abc\nefg"
abc
efg

CR無しなので、カーソルは行頭に戻らず、次の行に移動しています。

では、この出力が文字コードとしてはどのようになっているか確認してみます。
abcとefgの行を選択して、コピーして、以下のコマンドを実行してみました。

$ pbpaste | xxd
0000000: 2020 2020 2020 2020 2020 2020 2020 2020
0000010: 2020 2020 6162 630a 2020 2020 2020 2020 abc.
0000020: 2020 2020 2020 2020 2020 2020 2020 2065 e
0000030: 6667 0a fg.

すると、空白の部分は\x20、つまり半角スペースで満たされていることが確認できます。
この結果から、OS側がLFをタイプライター時代のLFと解釈するならば、画面出力の状態を保持するメモリにも\x20が書き込まれると考えました。
CRしてからLFすれば無駄はないのですが、LFしてからCRすると、本来不要な\x20を無駄にメモリに書き込むことになってしまうと想像しました。

元の文字コードとしては"6162 630a 6566 670a"です。
それを画面出力する際には空白部分が\x20で満たされた状態に展開されます。
空白部分が\x20で満たされた状態は、一時的かもしれませんが、メモリに保持しておく必要があるのかなと。

シェル/ttyドライバ/端末の切り分けはしてませんが、そのように考えて書きました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元