シェルスクリプトの展開

shellscript


texファイルがincludeしているファイルの一覧をsedで抽出して うふんあはんしていたところ、想像通りの挙動をしなかった話です。

ケツ論

経緯

まずこんなtexファイル main.tex があったとしましょう。

\documentclass{jsarticle}
\begin{document}
\include{foo}
\include{bar}
\end{document}

ここでincludeしているファイルを操作したいので、 パスをsedで抽出します。

$ sed -ne 's/^\\include{\(.*\)}/\1.tex/p' main.tex
foo.tex
bar.tex
$

うまくいきました。 ここで抽出したファイル名のファイルに対して操作を行いたいので シェルスクリプト上では以下のような操作をするのですが

$ for file in `sed -ne 's/^\\include{\(.*\)}/\1.tex/p' main.tex`
... do
...   echo $file
... done
... 
$

何も表示されません。困った。

対策1

$()を使うと普通に動きます。

$ for file in $(sed -ne 's/^\\include{\(.*\)}/\1.tex/p' main.tex)
... do
...   echo $file
... done
... 
foo.tex
bar.tex

対策2

めっちゃエスケープすると期待通りにうごきます。

$ for file in `sed -ne 's/^\\\\include{\(.*\)}/\1.tex/p' main.tex`
... do
...   echo $file
... done
... 
foo.tex
bar.tex

うまくいきました。

この時点での推測

アーハンバッククォート使ったときは 命令文内のエスケープが展開されてから渡されるのね アイシーアイシー。

気づいた

上の抽出パターンですが、後半の\(などの部分はバックスラッシュエスケープしてません。 エスケープが展開されてから命令が渡されるなら、 たとえば\\hoge\(\)\hoge()となるはずです。 こうなるならsedの正規表現の意味が変わってしまい、期待した動作はしないはずです。 でも動作しました。

ではこのバックスラッシュをエスケープしたらどうなるでしょう。

$ for file in `sed -ne 's/^\\\\include{\\(.*\\)}/\\1.tex/p' main.tex`
... do
...   echo $file
... done
... 
foo.tex
bar.tex

うごきました。

つまり

バッククォートを使うと、文中の\\はすげえ速度で\に展開されます。 でも\\でない単一のバックスラッシュは展開されずそのまま\となります。 ふざけんな。

ケツ論

バッククォート許さない。

ちゃんと調べてない

ちゃんと調べたら展開の順序は分かると思うのですが面倒なので調べてません。 ごめんなさい。気が向いたら調べて追記します。

余談

このポストmarkdownで書いてるんですけど、そのなかでも バックスラッシュをバックスラッシュでエスケープしまくってました。 キレそう。