Non-modern Shell Programing (3)

2.3 トークン認識

シェルはファイルから、対話シェルの場合は端末から、sh -cあるいはsystem()の場合は文字列から、行として入力を読む。入力行は無制限の長さを持ちうる。入力行は2つのメジャーモードを用いて構文解析される。2つのメジャーモードとは、通常のトークン認識とヒアドキュメント処理である。

文法からio_hereトークンが認識されている時、次の改行トークンの直後の1個以上の後続行は、1つ以上のヒアドキュメントの本文を形成し、ヒアドキュメント規則に従って構文解析されるものとする。

io_hereを処理していない時、シェルは入力の次の文字に下記の規則のなかで適用可能な最初の規則を適用することで入力をトークンに分解するものとする。トークンは入力の現在位置から下記の規則の1つによって区切られるまでとする。クォートする文字群も含め、トークンを形成する文字群は正確に入力そのままである。トークンが区切られると指示され、トークンに1つも文字が含まれないなら、実際のトークンが分割されるまで処理は継続するものとする。

  1. 入力終端が認識されたら、現在のトークンが区切られる。現在のトークンが存在しなければ、入力終端インジケータがトークンとして返されるものとする。
  2. 前の文字が演算子の部分として用いられており、現在の文字がクォートされておらず、演算子を形成するために現在の文字群とともに用いることができるならば、(演算子トークンの部分として用いられるものとする。
  3. 前の文字が演算子の部分として用いられており、現在の文字が現在の文字列とともに演算子を形成するために用いることができない場合、前の文字を含む演算子は区切られるものとする。
  4. 現在の文字が<バックスラッシュ>やシングルクォートやダブルクォートで、クォートされていないなら、クォートされたテキストの終端まで後続文字群をクォートする影響を及ぼすものとする。クォートの規則はクォートに述べられている。トークン認識の間、実際は置換は実行されないものとする。そして、結果のトークンは(<改行>の連結を除いて)入力に現れた文字群を正確に含んでおり、埋め込まれたクォートや置換演算子、囲んでいるクォートや置換演算子を含めて、<クォテーションマーク>とクォートされたテキストの終端の間が変更されないものとする。トークンはクォートされたフィールドの終端によって区切られないものとする。
  5. 現在の文字がクォートされていない'$'か'`'ならば、クォートされていない開始文字群から、シェルはパラメータ展開やコマンド置換や算術展開のどの候補の始端かを識別するものとする。開始文字群はそれぞれ、'$' または"${"、"$("または'`'、"$(("である。シェルは(引用されている節に説明されているように)展開される単位の終端を決定するために十分な入力を読むものとする。文字群を処理している間に、展開の実体やクォートが置換内で入れ子になっているのが発見された場合、発見された構文で指示されている方法で、シェルは再帰的に処理するものとする。置換の始端から終端までが発見された文字群は、埋め込まれた構文の認識に必要な全ての再帰を許し、埋め込まれた置換演算子やクォート、囲んでいる置換演算子やクォートを含めて、結果のトークンに変更されずに含まれるものとする。トークンは置換の終端によって区切られないものとする。
  6. 現在の文字がクォートされておらず、新しい演算子の最初の文字として用いることができるならば、現在のトークンは(もしあれば)区切られるものとする。現在の文字は次の(演算子トークンの始端として用いるものとする。
  7. 現在の文字がクォートされていない<改行>ならば、現在のトークンは区切られるものとする。
  8. 現在の文字がクォートされていない<空白>ならば、前の文字を含むトークンが区切られ、現在の文字は捨てられるものとする。
  9. 前の文字が単語の部分だったならば、現在の文字はその単語に追加されるものとする。
  10. 現在の文字が'#'ならば、それとそれに続く次の<改行>までの文字群(ただし<改行>は含まない)はコメントとして捨てられる。行を終端する<改行>はコメントの部分として見なされない。
  11. 現在の文字は新しい単語の始端として用いられる。

トークンが区切られたら、シェル文法の文法によって必要とされるように分類される。

SUSv4 - Shell Command Language - 2.3 Token Recognition

Non-modern Shell Programing (2)

書く気力が失せたので、SUSv4のShell Command Languageの翻訳でお茶を濁すことにする。基本的に直訳なので翻訳の質についてはご容赦。

2.2 クォート

クォートは、ある文字群や単語のシェルにとって特別な意味を除去するために用いる。クォートは、次の段落に示す特別な文字の字義通りの意味を保ったり、予約語予約語として認識されることをさまたげたり、ヒアドキュメントの処理内でパラメータ展開やコマンド置換をさまたげるために用いることができる。

以下の文字群それ自体を表現したいならば、アプリケーションはそれらをクォートするものとする。

| & ; < > ( ) $ ` \ " ' <空白> <タブ> <改行>

また、以下はある状況の下ではクォートする必要があるかもしれない。POSIX.1-2008のこの巻のどこかで述べられている条件により、これらの文字は特別になるかもしれない。

* ?[ # ~ = %

エスケープ文字とシングルクォートとダブルクォートいうさまざまなクォート機構がある。ヒアドキュメントはクォートのまた別の形式を表現する。

2.2.1 エスケープ文字(バックスラッシュ)

クォートされていない<バックスラッシュ>は、<改行>を除いて、続く文字の字義通りの意味を保つ。<改行> が<バックスラッシュ>に続く場合、シェルは行が継続していると解釈するものとする。<バックスラッシュ>と<改行>は入力をトークンに分割する前に除去されるものとする。エスケープされた<改行>は入力から完全に削除され、空白に置換されないので、トークンのセパレータとして振る舞うことはできない。

2.2.2 シングルクォート

シングルクォート('')で囲んだ文字群は、シングルクォート内のそれぞれの文字の字義的な意味を保つものとする。シングルクォートはシングルクォート内に現れられない。

2.2.3 ダブルクォート

ダブルクォート("")で囲んだ文字群は、下記に示すように、バッククォートと<ドル記号>と<バックスラッシュ>という文字群を除き、ダブルクォート内のすべての文字群の字義通りの意味を保つものとする。

$

<ドル記号>はパラメータ展開やコマンド置換の形式の1つや算術展開を開始するという特別な意味を保つものとする。

クォートされた文字列内の入力文字群は、"$("とそれにマッチする')'で囲まれていたら、ダブルクォートの影響を受けるのではなく、単語が展開される時にその出力で"$(...)"を置き換えるコマンドを定義するものとする。トークン認識のトークン化規則は、エイリアス置換のエイリアス置換を除いて、マッチする')'を再帰的に検索するように適用されるものとする。

"${"とそれにマッチする'}'で囲んだ文字群からなる文字列内では、もしあるとしたら偶数個の非エスケープのダブルクォートやシングルクォートが存在するものとする。先行する<バックスラッシュ>文字は'{'や'}'というリテラルエスケープするために用いられるものとする。パラメータ展開の規則がマッチする'}'を決定するために用いられるものとする。

`

バッククォートはコマンド置換の形式のもう1つを開始するという特別な意味を保つ。最初のバッククォートと、<バックスラッシュ>によって先行されていない次のバッククォートまでの文字群からなる、クォートされていない文字列の部分は、エスケープされた文字列を取り除いてから、単語が展開される時にその出力で"`...`"を置き換えるコマンドを定義する。下記のいずれかの場合は未定義の結果を引き起こす。

  • "`...`"内において、開始されるが終端しないシングルクォートまたはダブルクォート文字列
  • 同じダブルクォート文字列内において、開始するが終了しない"`...`"列

\

特別と見なされる下記の文字群の1つが続く時にのみ、<バックスラッシュ>はエスケープ文字として特別な意味を保つ。

$ ` " \ <改行>

アプリケーションはダブルクォート内に含まれるダブルクォートが<バックスラッシュ>によって先行されることを保証するものとする。'@'パラメータは特殊パラメータに述べられているように、ダブルクォート内でも特別な意味を持つ。

SUSv4 - Shell Command Language - 2.2 Quoting

Non-modern Shell Programing (1)

Heirloom Bourne ShellとSingle Unix Specificationを教科書に、現代的でないシェルプログラミングについて話す。

#! /bin/sh

set -eu

p() {
    printf '[ %s ]' "$@"
    printf '\n'
}

x=foo
y=bar
p "`p \"\`p \\\"\\\\\\\\$x \\\\\\\\$y\\\"\`\"`"

ぼくたちがシェルだったとしたら、ぼくたちはシェルではないのだけれど、ぼくたちはたぶん以下のように出力する。

[ [ [ \foo \bar ] ] ]

現代的でないシェルプログラミングを始めるにあたって、ダブルクォートとバッククォートの複雑な関係、つまり"\...\"形式のコマンド置換について整理することから始める。現代的でないので、$(...)形式のコマンド置換や算術展開は考慮しない。なお、SUSv4のShell Command Languageを参照する。

ほんやくコンニャク

英辞郎第四版に同梱されたPDIC形式の辞書ファイルをStarDict形式の辞書ファイルに変換する方法について、いくつかの落とし穴に関するメモ。

  1. PDIC1行テキスト形式に変換する。
  2. UTF-16LEからUTF-8に変換する。
  3. stardict-editorで読めるようなタブ区切りテキストに変換する。
  4. stardict-editorでStarDict形式の辞書ファイルに変換する。

手間を考えるとWindowsで作業するのが面倒がない。

PDIC1行テキストに変換する。

UTF-16LEからUTF-8に変換する。

  • iconvでもなんでも。

stardict-editorで読めるようなタブ区切りテキストに変換する。

  • SP, SOLIDUS, SOLIDUS, SOLIDUS, SPをCHARACTER TABULATIONに変換する。
    • CHARACTER TABULATIONで終わっている行は、たぶんPDIC1行テキスト形式に変換した際に発音とかが落ちた行なので無視する。
  • SP, REVERSE SOLIDUS, SPをREVERSE SOLIDUS, LATIN SMALL LETTER Nに変換する。
    • それ以外のREVERSE SOLIDUSはYEN SIGNに変換する。
  • UNIX改行にする。

stardict-editorでStarDict形式の辞書ファイルに変換する。

  • GDKまわりが面倒なのでWindowsで適当になんとかする。
  • stardictをインストールするとProgram Files/Common Files/GDKもインストールされる。
  • stardict-editorを適当なディレクトリに入れる。
  • 変換する。

OmegaTで使ったり、DictUnifierで使ったりして幸福になれる。

シェルのクォート

ダブルクォート内のバックスラッシュによるクォートのルールは、C由来のクォート規則と異なる。\\newlineの箇所は原文が誤っているように思う。

The following characters have a special meaning to the shell and cause termination of a word unless quoted.

; & ( ) | ^ < > newline space tab

A character may be quoted by preceding it with a \. \\newline is ignored. All characters enclosed between a pair of quote marks (''), except a single quote, are quoted. Inside double quotes ("") parameter and command substitution occurs and \ quotes the characters \ ` " and $.

下記の文字はシェルにとって特別な意味を持ち、クォートされていなければワード終端を引き起こす。

; & ( ) | ^ < > newline space tab

任意の文字は先行する\によってクォートされうる。\\newlineは無視される。シングルクォートで囲まれた、シングルクォートを除くすべての文字はクォートされる。ダブルクォート内ではパラメータ置換及びコマンド置換が行われ、\は\ ` " $をクォートする。

Manual Page - sh(1)

bash(1)のほうが判りやすい。それにしても、クォートにあてる良い訳語はないものかな。

ダブルクォートで文字を囲むとクォート内部の全ての文字は文字としての値を保持しますが、$, `, \は例外となります。$と`はダブルクォートの内部でも特殊な意味を失いません。バックスラッシュの場合は、次の文字が$, `, ", \, のいずれかである場合に限り特殊な意味を失いません。前にバックスラッシュを付ければ、ダブルクォート文字をダブルクォートによるクォートの内部でクォートできます。

http://www.linux.or.jp/JM/html/GNU_bash/man1/bash.1.html#lbAQ

たとえば、シェルスクリプトディレクトリを完全パスで取得したいとする。パスが空白を含む場合を考えると、下記の二行を一行にまとめることは相当面倒だ。

here=`dirname "$0"`
here=`(cd "$here" && pwd)`

dirnameの可搬性についてはhttp://www.geocities.jp/fut_nis/html/autoconf-ja/Limitations-of-Usual-Tools.htmlを参照。

シェルの関数内関数

シェルスクリプトは古き良きBourne Shellで書くものであり、Manual Page - sh(1)を参照するのが良い。SUSv4のShell Command Languageでは高機能すぎる。ふと気づいたが、古き良きBourne Shellであっても関数内関数が使える。

foo() {
    f() { echo foo; }
}

bar() {
    f() { echo bar; }
}

foo
f
bar
f

実行すると(Heirloom Bourne Shellbashkshでは)下記のように表示された。

foo
bar

何がうれしいのかと問われると、プロトタイプ継承みたいなことをするに際して、すこしうれしい場面があるように思う。

C++0xのPOD

id:faith_and_braveさんが解説されている。

PODには、最適化を促進する場合と阻害する場合がある。memcpyやmemmoveが使えるために高速化される場合もあれば、コンパイラによるレイアウトの最適化が勝つ場合もあるだろう。難しい。