マクロツイーター

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

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

前回の続き)
extractbb からパイプ入力する場合

実は話はここでもまだ終わってないのである。何故かと言えば、最近の graphicx パッケージの dvipdfmx 用ドライバファイルでは extractbb の出力をパイプ入力を利用して読み込んでいるからである。

\input "|extractbb -O tiger.png"\relax

TeX エンジンのパイプ入力の仕様は以下のようである。

  • \input\openin で指定されたファイル名の瀬能が「|」であればパイプ入力と解釈される。((ちなみに、拡張プリミティブでファイルを読み込むもの、例えば \pdfmdfivesum 等についてはパイプ入力は使用できない。))
  • コマンド実行の許可およびコマンド行の扱いは \write18 の場合と同じ規則が適用される。すなわち制限付シェルエスケープ有効の場合はサニタイジングが行われる。*1

これだけ見ると、通常のコマンド実行と全く同様に対処できるように見える。ところが実は、制限付シェルエスケープ有効の場合には、「TeX 上での引数の扱いの違い」と絡んで重大な問題を引き起こすのである。

(前回述べたように)通常のコマンド実行では、空白入りのファイル名はダブルクオートで囲う必要があった。(シングルクオートは不可。)

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

ところが、\write が「波括弧で囲う」引数を取るのに対して、\input は「ダブルクオートで囲う」引数を取る。従って、両者のダブルクオートの使用が衝突してしまうのである。

% ダメ
\input "|extractbb "to ra.png""\relax

ここでマクロや“特殊なカテゴリコードの文字トークン”を用いてこの問題を回避するのは困難だと思われる。((\input の引数には文字トークンしか含めることができず、またその終結判定処理は文字コードのみで行われるため、最終的に“ファイル名”として使われる文字列にダブルクオートを含めることは不可能だと思われる。))また制限付シェルエスケープなのでコマンド行で何か細工をすることも不可能であろう。

なお、無制限シェルエスケープでかつ標準シェルが bash(か sh)の場合なら、シングルクオートを用いるなどの方法で、空白入りファイル名を扱える。

% 無制限シェルエスケープ有効の時のみ使用可能
\input "|extractbb -O 'to ra.png'"\relax
Windows の場合の裏技

そして、実はなんと、このシングルクオートで囲った形式が Windows でも通用するのである。そしてこの場合は制限付シェルエスケープでも成功する!

% Windows なら制限付シェルエスケープ有効でも使用可能
\input "|extractbb -O 'to ra.png'"\relax

なぜこんな動作になるかというと、先に述べた「パイプ入力の時のコマンド行の解釈の規則」に実は一つ例外があるからである。

  • Windows の場合は事前に〈'〉を〈"〉に置換する。

つまり上のパイプ入力を実行した場合に実際にコマンド行になるのは「extractbb -O "to ra.png"」となる。無制限シェルエスケープならこれがそのままコマンドプロンプトに渡されるので想定通りに動く。制限付シェルエスケープの場合でも、「空白を含む引数はダブルクオートで囲う」という“制限時のコマンド行規則”に適うのでやっぱり成功する。

Windows でのみ例外が設けられた目的は、「コマンドプロンプトでは空白文字を囲うのにダブルクオートが必要」という仕様と「\input で引数にダブルクオートが必要」という仕様が衝突するのを解決するためであると、私は推測している。しかし、もしその推測が正しいのであれば、制限付シェルエスケープ有効の場合は環境を問わずにクオートの置換処理(〈'〉→〈"〉)を行うべきだと私は思う。なぜなら、コマンドプロンプトと場合と全く同じ理由で衝突が起こっているからである。((なお、制限付シェルエスケープのコマンド行では元々〈'〉は全面的に禁止されているので、この処置により何かが実行できなくなることはない。また、この置換を行ったことで実行できるコマンド行は、最初から〈"〉を用いれば元々 \write18 で実行できるものであるので、この処置によりセキュリティのリスクが高まることはない。))

*1:無制限シェルエスケープ有効の場合は指定したままのコマンド行が実行されるし、シェルエスケープ無効の場合はパイプ入力も一切使えない。