ShellScript
tar

tar fileをシェルスクリプトに埋め込む

概要

普通、シェルスクリプトはシェルコマンドを列挙して動作を記述するスクリプトですが、tarなどを用いて圧縮したファイルのバイナリデータを埋め込んでやることで単体のファイルで動作可能なオフラインインストーラにできたり、何らかのファイルを扱う処理を単体で記述できたりします。これ自体は有名な気がするのですが、日本語の情報がなぜか少なかったので書いておきます。"shell script embedded tar file"とかググると英語情報はいっぱい出ます。
私が見たことある例だと、AnacondaのLinux向けインストールスクリプトはAnacondaの構成ファイルすべてをスクリプトに埋め込んであるため、シェルスクリプトでありながら637MBものサイズがあります。

環境

  • zsh

手法

とりあえず以下のようなシェルスクリプトを用意します。

tarball1.sh
#!/bin/zsh

TAR_START=$(awk '/^__TARFILE_FOLLOWS__/ { print NR + 1; exit 0; }' $0)

tail -n +${TAR_START} $0 | tar -zpvx

exit 0

__TARFILE_FOLLOWS__

適当なtar.gzファイルを用意します。ここでは以下のVimアイコンを圧縮して調達します。
vim-icon.png

$ tar -zcf ./tarfile.tar.gz ./vim-icon.png

次に、以下のコマンドでシェルスクリプトの末尾にtar.gzファイルを埋め込みます。

$ cat ./tarfile.tar.gz >> ./tarball1.sh 

これだけです。これで、tarball1.shを実行してやるとカレントディレクトリにvim-icon.pngが展開されるようになります。

仕組み

tarball1.sh
#!/bin/zsh

TAR_START=$(awk '/^__TARFILE_FOLLOWS__/ { print NR + 1; exit 0; }' $0)

tail -n +${TAR_START} $0 | tar -zpvx

exit 0

__TARFILE_FOLLOWS__

改めてこのシェルスクリプトを見て、仕組みを解説していきます。

まず、この箇所です。

TAR_START=$(awk '/^__TARFILE_FOLLOWS__/ { print NR + 1; exit 0; }' $0)

この箇所では、TAR_START変数に、awkコマンドの出力結果を格納しています。
awkコマンドの構造はざっくり書くとこんな感じです。

awk '/^[この文字から始まる行]/ {print NR + 1; exit 0;}' filename

つまり、filenameで指定したファイルの中で、1つ目の指定で指定した文字列と合致する行をまず抜き出し、その結果が自動で格納される組み込み変数NRに1を加算した結果をprintしています。なお、$0というのはコマンドライン引数の取得リテラルで、0番目は自分自身のファイル名を指します。
今回のスクリプトのawkの場合は__TARFILE_FOLLOWS__次の行を返すことになります。後にtarバイナリが入る先頭行ですね。

次にこの行です。

tail -n +${TAR_START} $0 | tar -zpvx

tailコマンドの方から見ていきます。この場合のtailコマンドの構造は以下のようなものです。

tail -n +N filename

この場合、tailコマンドはfilenameで指定したファイルのN行目以降を抜き出して出力します。
つまり、今回のスクリプトではcatで埋め込んだtarバイナリのみが切り出されます。

そして、その結果を以下のtarコマンドにパイプで渡しています。

tar -zpvx

これはおなじみで、渡されたファイルのバイナリを見て展開可能なファイルであれば展開を行ってくれます。

最後にこいつです。

exit 0

普段わざわざシェルスクリプトの最後にexitを書いて明示終了することはあまりありませんが、今回の場合はこれを書いてやらないと、それ以降の__TARFILE_FOLLOWS__やtarバイナリがコマンドとして実行されてしまいエラーになるので、書かなければなりません。

利用例

普通にインストーラや展開装置として便利なのですが、ちょうどこの記事の中で面白い使い方をしたので例に挙げます。

makeimg.sh
TAR_START=$(awk '/^__TARFILE_FOLLOWS__/ { print NR + 1; exit 0; }' $0)

tail -n +${TAR_START} $0 | tar -C /tmp -zx

cat ./$1 > $2
cat /tmp/disktmp.bin >> $2

rm /tmp/disktmp.bin

exit 0
__TARFILE_FOLLOWS__
# バイナリデータは略(というか書けない)

このスクリプトは、ブータブルディスクのブートセクタバイナリとそれ以外の領域のバイナリを結合して一つのブータブルディスクイメージを生成するスクリプトです。簡単に言うと、2つのバイナリファイルをそのまま結合して出力してくれるものです。しかし、結合する一方のファイルは不変であるため、スクリプトに埋め込んで外部ファイルに依存しないようにしたのです。
実際は以下のように使えます。

sh ./makeimg.sh [結合するバイナリ名] [出力バイナリ名]

実行すると、[結合するバイナリ名]に渡したファイルの後ろに、スクリプト内に埋め込まれたtarを展開したバイナリが結合されて、[出力バイナリ名]に指定した名前でカレントディレクトリに出力されてきます。

この場合は展開されるファイルそのものがほしいわけではなく、一時的に展開したファイルを利用して得た成果物がほしいだけなので、tarコマンドに-Cオプションを適用して/tmpディレクトリに一時ファイルを作成しています。

おしまい。