【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

パイプ進捗表示ツール

ファイルを標準出力に転送しながら進捗を表示する小さなツールを作成しました。

使用例
% sizecat src.tar.bz2.1 src.tar.bz2.2 | bzcat | tar xf -
 49% [#########################-------------------------] 92/185 KB

上例のようにアーカイブを展開する時、ファイルが巨大で長時間待たされると進捗が気になります。今まではtarのvオプションで処理中のファイルを表示させていましたが、あまりに多いと参考になりません。そのためシンプルな進捗表示がしたくなり作成しました。

ソース

POSIX汎用でパブリックドメインです。

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

#define BUFMAX 65536
static char buf[BUFMAX];

void logout(FILE *f, int pos, int size, int nosize)
{
    char bar[51];
    int i, percent, size2 = size / 100;
    
    if (size2 < 10000)
        percent = pos * 100 / size;
    else
        percent = pos / size2;
    if (percent > 100) percent = 100;
    
    for (i = 0; i < 50; i++)
        bar[i] = i * 2 < percent ? '#' : '-';
    bar[50] = '\0';
    if (nosize == 0)
        fprintf(f, "\r%3d%% [%s] %d/%d KB", percent, bar, pos / 1024, size / 1024);
    else
        fprintf(f, "\r%3d%% [%s]", percent, bar);
}

int usage(const char *argv0)
{
    fprintf(stderr, "usage: %s [-nosize] file [...]\n", argv0);
    return 1;
}

int getbufsize(int size)
{
    int sz = size / 50, a = 16, ret = 16;
    if (sz <= 16) return 16;
    if (sz >= BUFMAX) return BUFMAX;
    for (; a < sz; a <<= 1) ret = a;
    return ret;
}

int main(int argc, char *argv[])
{
    int i, size = 0, sizekb, nosize = 0, pos = 0, bufsize;

    if (argc < 2) return usage(argv[0]);

    if (!strcmp(argv[1], "-nosize"))
    {
        if (argc < 3) return usage(argv[0]);
        nosize = 1;
    }
    
    for (i = nosize + 1; i < argc; i++)
    {
        struct stat st;
        if (stat(argv[i], &st) != 0)
        {
            fprintf(stderr, "ERROR: can not get state: %s\n", argv[i]);
            return errno;
        }
        size += st.st_size;
    }
    sizekb = size / 1024;
    bufsize = getbufsize(size);

    for (i = nosize + 1; i < argc; i++)
    {
        logout(stderr, pos, size, nosize);
        FILE *f = fopen(argv[i], "rb");
        if (f == NULL)
        {
            fprintf(stderr, "\nERROR: can not open: %s\n", argv[i]);
            return errno;
        }
        while (!feof(f))
        {
            int len = fread(buf, 1, bufsize, f);
            fwrite(buf, 1, len, stdout);
            pos += len;
            logout(stderr, pos, size, nosize);
        }
        fclose(f);
    }

    fprintf(stderr, "\n");
    return 0;
}

仕組み

進捗情報は"\r"で行頭に戻って上書きしています。イメージを示します。

fprintf(stderr, "\r0%");
// 重い処理
fprintf(stderr, "\r50%");
// 重い処理
fprintf(stderr, "\r100%\n");