% perl -e 'open(OUT,">normal.dat");print OUT "a" x 1000000'
⇒ normal.dat は 'a' で埋まったファイル
% perl -e 'open(OUT,">zero.dat");for(1..1000000){printf OUT "%c", 0}'
⇒ zero.dat は NULL (0x00) で埋まったファイル
% perl -e 'open(OUT,">sparse.dat");seek(OUT,999999,0);printf OUT "%c", 0'
⇒ sparse.dat は、1MB 弱シークで移動し、NULL (0x00) を出力したファイル
作成されたファイルを ls で見てみよう。ただし -s オプションを付けて、消費ブロックサイズを表示する。
% ls -ls
1008 -rw-r--r-- 1 user group 1000000 Jun 25 14:57 normal.dat
1008 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 zero.dat
32 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 sparse.dat
% cp sparse.dat sparse2.dat
% ls -l
32 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 sparse.dat
1008 -rw-r--r-- 1 user group 1000000 Jun 25 14:56 sparse2.dat
tar も同様で、tar cf でアーカイブを作成し、tar xf で展開すると、穴あきファイルは普通の NULL 埋めファイルになってしまう。
これは cp や tar が悪いのではない。穴あき部分を read(2) しても、NULL (0x00) が連続しているデータとして読み込まれるので、各コマンド側から穴あきファイルかどうかを判断することはできないからである。どうしても穴あきファイルをみわけたければ、ブロックサイズから計算した実サイズと、みかけのサイズを比較するか、dump コマンドのようにデバイスを直接参照するしかない。
GNU tar ではアーカイブ作成時に -S オプションをつけると穴あきファイルを「穴あきファイルとして」アーカイブするこができる。ただし、この方法で作成したアーカイブは POSIX 標準 tar フォーマットではないため、GNU tar 以外では展開できないだろう。また、GNU の cp はデフォルトで穴あきファイルを正しくコピーできる。この挙動は --sparce オプションで変更できる。GNU tar と GNU cp は、前述の実サイズとみかけのサイズを比較する方法で穴あきファイルかどうかを判断している。
…と書いたのは 2002年頃だった気がするが、2000年代前半、VMware などの仮想マシンの VM イメージで使われ、2000年代後半はクラウドでの VM イメージなどで使われ、2010年代は Docker イメージでも使われ、結果としては穴あきファイルは必要なものである、ということを歴史が証明したと言えよう。