tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

Re: テキストファイルの特定行を取得する方法

テキストファイルの特定行を取得する方法 - つれづれなるままに

sed を使う方法

sed は、ストリームエディタの略。

ファイルを対象に、行単位で加筆/修正/削除するツール。

# i=1
# sed -n "${i}P" /etc/hosts

とコマンドを打てば、指定行(例では /etc/hosts の 1 行目)が表示される。

ループさせて、その1行の内容ごとに条件分岐させるとき、このやり方を使ってる。

head と tail を組み合わせる方法

head コマンドは、ファイルを上から n 行目まで表示させる。

tail コマンドは、ファイルを下から n 行目まで表示させる。

head コマンドで上から n 行目まで表示して、パイプで繋げて下から 1 行目を表示すれば、特定の行のみが表示される理屈。

# i=1
# head -${i} /etc/hosts | tail -1

サンプルとしては、こんな感じ?

懸念される点として、1行ずつ取り出すけど、全体でン万行とか展開する必要があるとき、どれくらいタイムロスが発生するかなんだよね。

GNU の head のソースをざっくり見た感じでは、各行を fgets とかしてるわけではなく、表示しない行は lseek ですっ飛ばしているので心配なさげ。

でも一応いんちきベンチマークしてみました。

結論から言うと、head/tail も sed もそこそこ早いけど、sed のほうがちょっと早いっぽいです。

以下がいんちきベンチマークスクリプト

#!/usr/bin/perl
# http://d.hatena.ne.jp/KuroNeko666/20090520/1242810069
use strict;
use warnings;


for my $lines ( qw(100000 1000000 10000000) ) {
    my $filename = "${lines}.txt";
    write_file($lines);

    print "-----------------------------\n";
    print "$lines lines (head/tail)\n";
    system("sh -c \"time head -$lines $filename | tail -1\"");

    print "$lines lines (sed -n)\n";
    system("sh -c \"time sed -n ${lines}P\ $filename\"");
}

sub write_file {
    my ($lines) = @_;
    open(my $OUT, '>', "${lines}.txt");
    for ( my $i=1; $i<=$lines; $i++ ) {
        print {$OUT} "$i\n";
    }
    close($OUT);
}

行番号が書かれたファイルを作り、それの末尾を head/tail と sed で読んでみて、time コマンドで実行時間を取ります。行数は10万、100万、1000万行でやってみました

実行環境ですが、FreeBSD(head/tail/sed はシステム付属) と cygwin(head/tail/sed はシステム付属でGNU由来)でやってみました。

FreeBSD の場合
tsucchi@over[111]% perl a.pl
-----------------------------
100000 lines (head/tail)
        0.02 real         0.00 user         0.01 sys
100000
100000 lines (sed -n)
100000
        0.02 real         0.01 user         0.00 sys
-----------------------------
1000000 lines (head/tail)
        0.27 real         0.00 user         0.15 sys
1000000
1000000 lines (sed -n)
1000000
        0.17 real         0.15 user         0.00 sys
-----------------------------
10000000 lines (head/tail)
        3.26 real         0.01 user         1.73 sys
10000000
10000000 lines (sed -n)
10000000
        1.89 real         0.01 user         1.79 sys
cygwin の場合
tsucchi@appears[101]% perl a.pl
-----------------------------
10000 lines (head/tail)
10000

real    0m0.259s
user    0m0.092s
sys     0m0.062s
10000 lines (sed -n)
10000

real    0m0.179s
user    0m0.046s
sys     0m0.031s
-----------------------------
100000 lines (head/tail)
100000

real    0m0.095s
user    0m0.060s
sys     0m0.092s
100000 lines (sed -n)
100000

real    0m0.061s
user    0m0.061s
sys     0m0.016s
-----------------------------
1000000 lines (head/tail)
1000000

real    0m0.398s
user    0m0.170s
sys     0m0.388s
1000000 lines (sed -n)
1000000

real    0m0.395s
user    0m0.390s
sys     0m0.030s
-----------------------------
10000000 lines (head/tail)
10000000

real    0m4.788s
user    0m1.592s
sys     0m4.342s
10000000 lines (sed -n)
10000000

real    0m3.836s
user    0m3.718s
sys     0m0.155s

10万行、100万行ではほとんど差は無い感じ。1000万行では sed の方が早いみたいです。自分は head/tail 派だったのだけど、今後は sed を使ってみようかなぁ。