大量のシンボリックリンクファイルをGzipで圧縮する

大量のシンボリックリンクファイルをGzipで圧縮する

大量のシンボリックリンク ファイルを含むフォルダーがあります。これらのファイルはそれぞれ 10 ~ 11 GB 程度です (具体的には fastq ファイル)。これらはさまざまなソース フォルダーから取得されますが、シンボリックリンクのレベルは 1 つだけであることを確認しました。

私は単純に次のようにしてそれらを gzip 圧縮しようとしています:

gzip *.fastq

その結果、

too many levels of symbolic links

そして失敗します。

しかし、私がそうすると:

for i in `ls | egrep *.fastq$`; do gzip -c $i > $i.gz; done;

確かに動作します。私の質問は簡単です。これらの違いは何ですか? 私の知る限り、唯一の違いは、2 番目のアプローチではファイルごとに新しい gzip プロセスを開始するのに対し、最初のアプローチではすべてを 1 つのプロセスで実行することです。gzip は一度に 1 つのシンボリックリンク ファイルしか処理できませんか? 通常のファイルを含むテスト フォルダーで同じことを行うと、どちらの方法でも動作します。

答え1

gzipソース(具体的にはUbuntu 14.04に含まれるgzip 1.6)を簡単に確認すると、観察された動作は次の関数から来ていることがわかります。オープンと統計gzip.c の 1037 行目から始まります:

static int
open_and_stat (char *name, int flags, mode_t mode, struct stat *st)
{
  int fd;

  /* Refuse to follow symbolic links unless -c or -f.  */
  if (!to_stdout && !force)
    {
      if (HAVE_WORKING_O_NOFOLLOW)
        flags |= O_NOFOLLOW;
      else
        {
#if HAVE_LSTAT || defined lstat
          if (lstat (name, st) != 0)
            return -1;
          else if (S_ISLNK (st->st_mode))
            {
              errno = ELOOP;
              return -1;
            }
#endif
        }
    }

  fd = OPEN (name, flags, mode);
  if (0 <= fd && fstat (fd, st) != 0)
    {
      int e = errno;
      close (fd);
      errno = e;
      return -1;
    }
  return fd;
}

コメント行には、gzip は -c または -f フラグで呼び出されない限りシンボリック リンクをたどらないことが記述されており、#if ... #endif 内では、圧縮対象のファイルが実際にシンボリック リンクである場合、errno 変数が ELOOP (検出されたシンボリック リンクが多すぎます) に設定されることに注意してください。

さて、gzip(1)のマニュアルページによると、-cフラグと-fフラグは次のようになります。

   -c --stdout --to-stdout
         Write  output  on  standard output; keep original files unchanged.  If there are
         several input files, the output consists of a  sequence  of  independently  com‐
         pressed  members.  To  obtain  better  compression,  concatenate all input files
         before compressing them.


  -f --force
         Force compression or decompression even if the file has multiple  links  or  the
         corresponding  file  already  exists,  or if the compressed data is read from or
         written to a terminal. If the input data is not in a format recognized by  gzip,
         and  if the option --stdout is also given, copy the input data without change to
         the standard output: let zcat behave as cat.  If -f is not given, and  when  not
         running  in  the  background,  gzip  prompts  to verify whether an existing file
         should be overwritten.

すべてをまとめると、元の質問に戻ります。

  • 最初の例は、実際のシンボリックリンクを圧縮しようとしているため失敗します(ない実際のリンクループ)
  • 2 番目は -c フラグを使用しているため、元のファイルの内容を読み取り、圧縮された出力を stdout に書き込むため、成功します。
  • 3 番目のシナリオは、-c の代わりに -f を使用することです。この場合、gzip はシンボリック リンクを圧縮しようとしてもエラーを出力しませんが、解凍すると次のように通常のファイルになります。
$ ls -l
合計 4
-rw-rw-r-- 1 x86tux x86tux 13 6月 16 13:10 realfile.txt
lrwxrwxrwx 1 x86tux x86tux 12 Jun 16 23:40 symlink.txt -> realfile.txt
$ gzip シンボリックリンク.txt
gzip: symlink.txt: シンボリックリンクのレベルが多すぎます
$ gzip -f シンボリックリンク.txt
$ ls -l
合計 8
-rw-rw-r-- 1 x86tux x86tux 13 6月 16 13:10 realfile.txt
-rw-rw-r-- 1 x86tux x86tux 45 6月16日 13:10 symlink.txt.gz
$ gunzip シンボリックリンク.txt.gz
$ ls -l
合計 8
-rw-rw-r-- 1 x86tux x86tux 13 6月 16 13:10 realfile.txt
-rw-rw-r-- 1 x86tux x86tux 13 6月 16 13:10 symlink.txt
$ md5sum *
618f486e0225d305d16d0648ed44b1eb 実ファイル.txt
618f486e0225d305d16d0648ed44b1eb シンボリックリンク.txt

答え2

ファイルごとに 1 つのプロセスを実行する部分は、操作のボトルネックになる可能性がある場合は多少問題になるかもしれませんが、10 ~ 11 ギガバイトの場合、呼び出しによってexec進行gzipが妨げられるシナリオを想像するのは非常に困難です。

同様に、小さなファイルが多数ある場合は、gzipファイルごとに比較するデータが少なくなるため、圧縮できない可能性が高くなりますが、この場合も、圧縮操作ごとに 10 ~ 11 ギガバイトなので、問題にはなりません。

ただし、エラーの原因を発見するのは興味深いと思います。lsofバックグラウンドのgzippid に適用して、何が起こっているのか調べることをお勧めします。

関連情報