マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

空白文字入りファイル名の画像を extractbb したい話 (1)

「graphicx パッケージ + dvipdfmx ドライバの環境において、空白入りのファイル名をもつ画像ファイルを扱えるようにする」という課題について、少し真剣に考えてみる。最大の難点は「extractbb の自動実行」の部分であろうから、まずそれについて考えることにする。

さっそく結論…?

えーと、extractbb は空白文字入りファイル名に対応しているので

[コマンドプロンプトの場合]
extractbb "to ra.png"
[bashシェルの場合]
extractbb 'to ra.png'
extractbb "to ra.png"
extractbb to\ ra.png

でオッケー。めでたしめでたし。

……じゃない。これを TeX の中で実行したいのである。それも制限付シェルエスケープ(restricted shell escape)有効の時に。

無制限なシェルエスケープな場合

TeX の外部コマンド実行は必ずシェルを経由して行われる。((恐らく system() システムコールを単純に読んでいるんだと思う。))特に、シェルエスケープが無制限(-shell-escape 指定)である場合は、\write18 の引数の文字列*1がそのままシェルのコマンド行となる。だから、例えば Linux では“標準のシェル”が bash であるため、先に示した 3 つのコマンド行は全て shell escape で使用できる。((なお制御綴「\␣」は展開不能だから脱トークン化した結果はそのまま「\␣」という文字列になる。))

\immediate\write18{extractbb 'to ra.png'}
\immediate\write18{extractbb "to ra.png"}
\immediate\write18{extractbb to\ ra.png}
制限なシェルエスケープな場合

ところが、制限付(restricted)のシェルエスケープ有効(-shell-restricted 指定*2)の場合は、この仕様では都合が悪い。制限付シェルエスケープの目的は「特定のコマンドだけ実行を許可する」ことであり、このためにコマンド行の先頭の語(コマンド名)を検査している。しかし、“シェルのコマンド行の文法”が全て使用可能になってしまうと、それを利用して結局任意のコマンドが実行できてしまうのである。これでは“制限”の意味が成さなくなる。

% "そのまま"実行されると, touch も起動してしまう!
\immediate\write18{extractbb 'to ra.png' & touch you_are_an_idiot}

このため、制限付モードの場合は、コマンド行をサニタイジング(無害化)してからシェルに渡す、という動作になっている。*3具体的なサニタイジングの手順は、環境により(シェルの種類により)異なるが、実効的には、制限付シェルエスケープ有効の場合のコマンド行の「仕様」は以下のようになる。要するに「特殊文字がほとんど無い低機能なシェル」であるかのように動作するのである。

  • 空白文字、〈'〉、〈"〉以外を“通常の文字”と呼ぶことにする。
  • 原則は:
    • コマンド行には普通の文字と空白文字のみを含むことができる。
    • 行を空白(の列)で区切って“語”の列にする。先頭の語がコマンド名で、後続する語が引数となる。
    • コマンド名は、予め定められた“許可リスト”(Kpathsea 変数の shell_escape_commands)に含まれるものでなければならない。
  • ただし:
    • 1 つ以上の語の列の両端を〈"〉で囲うことで、囲った部分(〈"〉自体は含まない)を 1 つの引数と認識させることが可能。
    • コマンド名は〈"〉で囲うことができない。
    • 語の途中に〈"〉を入れることは不可。また、〈'〉は全く使えない。

この規則に従うと、例えば、次のコマンド行は〈'〉を含むので不正と見なされ失敗する(何も実行されない)。

\immediate\write18{extractbb 'to ra.png' & touch you_are_an_idiot}

次のコマンド行は、もし extractbb が“許可リスト”に含まれているならば、extractbb コマンドが「to ra.png」「&」「touch」「you_Are_an_idiot」の 4 つの引数を伴って実行される。

\immediate\write18{extractbb "to ra.png" & touch you_are_an_idiot}

※なお、Linux の場合、実際のサニタイジングの結果は以下のようになる(はず)。

extractbb 'to ra.png' '&' 'touch' 'you_are_an_idiot'

さて、今やりたいことは、「extractbb を空白入りのファイル名を伴って起動する」ことであった。先に述べた規則によると、実はこれは容易であり、次のようにすればよいことが判る。

\immediate\write18{extractbb "to ra.png"}

なお、ここで指定したコマンド行は、無制限シェルエスケープ有効の場合でも、“標準のシェル”がコマンドプロンプトか sh か bash であればそのまま通用する。

*1:正確にいうと、引数のトークン列を完全展開した後脱トークン化して得られる文字列。

*2:現在の多くの TeX 環境では 制限付シェルエスケープ有効が既定になっている。

*3:シェルを使わずにプロセスを起動する、ではないことに注意。