web-dev-qa-db-ja.com

ファイルからn行目を取得するためのbashツール

それを行うための「標準的な」方法はありますか。これまではうまくいきましたhead -n | tail -1を使ってきましたが、特にファイルから1行(またはある範囲の行)を抽出するBashツールがあるかどうか疑問に思いました。

「標準的」とは、その主な機能がそれを行っているプログラムを意味します。

486
Vlad Vivdovitch

巨大なファイルの場合、headtailを使用したパイプ処理は遅くなります。私はsedをこのように提案します:

sed 'NUMq;d' file

NUMは印刷したい行の番号です。たとえば、fileの10行目を印刷するには、sed '10q;d' fileを使用します。

説明:

行番号がNUMqの場合、NUMは直ちに終了します。

dはその行を表示せずに削除します。 qは終了時にスクリプトの残りの部分をスキップさせるため、これは最後の行では禁止されています。

変数にNUMがある場合、一重引用符の代わりに二重引用符を使用します。

sed "${NUM}q;d" file
648
anubhava
sed -n '2p' < file.txt

2行目を印刷します

sed -n '2011p' < file.txt

2011行

sed -n '10,33p' < file.txt

10行目から33行目まで

sed -n '1p;3p' < file.txt

1行目と3行目

等々...

Sedで行を追加するために、これをチェックすることができます。

sed:特定の位置に行を挿入します

255
jm666

私はこのページで提案されたソリューションをベンチマークすることができるという独特の状況を持っているので、私はこの答えをそれぞれの実行時間を含む提案されたソリューションの統合として書いています。

セットアップ

1行に1つのキーと値のペアを持つ3.261ギガバイトASCIIテキストデータファイルがあります。このファイルには合計3,339,550,320行が含まれており、私が試したVimも含めて私が試したどのエディタでも開くことができません。私が発見した値のいくつかを調査するには、このファイルをサブセット化する必要があります。

ファイルには非常に多くの行があるためです。

  • データに役立つことをするためには、行のサブセットだけを抽出する必要があります。
  • 私が気にする値に至るまでのすべての行を読むのは長い時間がかかるでしょう。
  • 解決策が私が気にしている行を越えて読み、ファイルの残りの部分を読み続けるならば、それはおよそ30億の無関係な行を読むのに時間を浪費し、必要以上に6倍長くかかるでしょう。

私の最善のシナリオは、ファイル内の他の行を読み込まずにファイルから1行だけを抽出するソリューションですが、これをBashでどのように実行するかについては考えられません。

私の正気のために私は私が私自身の問題のために必要とするであろう全500,000,000行を読むことを試みるつもりではないでしょう。代わりに、私は3,339,550,320行から50,000,000行を抽出しようとしています(つまり、全ファイルを読むのに必要な時間より60倍長い時間がかかります)。

各コマンドのベンチマークには、組み込みのtimeを使用します。

ベースライン

最初にheadtailソリューションがどのようにしているのか見てみましょう:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

5000万行のベースラインは00:01:15.321です。5000万行にまっすぐ進むと、おそらく約12.5分になります。

カット

私はこれには疑いがありますが、一撃の価値があります。

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

これは実行に00:05:12.156かかりました。これはベースラインよりはるかに遅いです。停止する前にファイル全体を読み終えるのか、それとも最大5000万行まで読み終えるのかはわかりませんが、これが問題の実行可能な解決策ではないようです。

_ awk _

フルファイルが実行されるのを待つつもりはなかったので、私はexitを使用してソリューションを実行しました。

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

このコードは00:01:16.583で実行されました。これはわずか1秒ほど遅くなりますが、それでもベースラインを改善するものではありません。この速度でexitコマンドが除外されていた場合、ファイル全体を読み取るのにおそらく約76分かかりました。

Perl

既存のPerlソリューションも実行しました。

$ time Perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

このコードは00:01:13.146で実行されていましたが、これはベースラインより約2秒高速です。私が500,000,000フルで実行した場合、おそらく約12分かかります。

sed

ボード上のトップの答えは、ここに私の結果です:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

このコードは00:01:12.705で実行され、ベースラインより3秒早く、Perlよりも最大0.4秒速くなっています。私がそれを全500,000,000行で実行するとしたら、おそらく約12分かかります。

マップファイル

私はbash 3.1を持っているので、mapfileソリューションをテストすることができません。

結論

ほとんどの場合、headtailソリューションを改善するのは難しいようです。せいぜいsedソリューションは効率を約3%向上させます。

(パーセンテージは式% = (runtime/baseline - 1) * 100で計算されます)

行数50,000,000

  1. 00:01:12.705(-00:00:02.616 = -3.47%)sed
  2. 00:01:13.146(-00:00:02.175 = -2.89%)Perl
  3. 00:01:15.321(+00:00:00.000 = + 0.00%)head|tail
  4. 00:01:16.583(+00:00:01.262 = + 1.68%)awk
  5. 00:05:12.156(+00:03:56.835 = + 314.43%)cut

行数500,000,000

  1. 00:12:07.050(-00:00:26.160)sed
  2. 00:12:11.460(-00:00:21.750)Perl
  3. 00:12:33.210(+00:00:00.000)head|tail
  4. 00:12:45.830(+00:00:12.620)awk
  5. 00:52:01.560(+00:40:31.650)cut

行数3,338,559,320

  1. 01:20:54.599(-00:03:05.327)sed
  2. 01:21:24.045(-00:02:25.227)Perl
  3. 01:23:49.273(+00:00:00.000)head|tail
  4. 01:25:13.548(+00:02:35.735)awk
  5. 05:47:23.026(+04:24:26.246)cut
78

awkを使えば、かなり高速です。

awk 'NR == num_line' file

これが当てはまる場合、awkのデフォルトの振る舞い{print $0}が実行されます。


代替バージョン

ファイルが巨大になっている場合は、必要な行を読んだ後にexitを使用したほうがよいでしょう。これにより、CPU時間を節約できます。

awk 'NR == num_line {print; exit}' file

Bash変数から行番号を与えたい場合は、次のようにします。

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent
44
fedorqui

うわー、すべての可能性!

これを試して:

sed -n "${lineNum}p" $file

あなたのAwkのバージョンに応じて

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

nawkまたはgawkコマンドを試す必要があるかもしれません ).

その特定の行を印刷するだけのツールはありますか?標準的なツールではありません。しかし、おそらくsedが最も近くて最も使いやすいです。

26
David W.
# print line number 52
sed '52!d' file

sedに便利な1行のスクリプト

20
Steven Penny

この質問はBashとタグ付けされています、これがBash(≥4)のやり方です:-s(スキップ)と-n(count)オプションでmapfileを使います。

fileというファイルの42行目を取得する必要がある場合は、次のようにします。

mapfile -s 41 -n 1 ary < file

この時点で、配列のaryにはfileの行(末尾の改行を含む)が含まれ、最初の41行(-s 41)はスキップされ、1行(-n 1)の読み取り後に停止します。だからそれは本当に42行目です。印刷するには:

printf '%s' "${ary[0]}"

行の範囲が必要な場合は、42から666の範囲(両端を含む)を言って、自分で計算をしたくないと言って、それらを標準出力に出力します。

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

これらの行も処理する必要がある場合は、末尾の改行を格納するのはあまり便利ではありません。この場合は-tオプション(trim)を使用してください。

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

あなたに関数がそれをするようにさせることができます:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

外部コマンドはなく、Bashだけが組み込まれています。

20
gniourf_gniourf

私のテストによると、パフォーマンスと読みやすさの観点から、私のお勧めは次のとおりです。

tail -n+N | head -1

Nは必要な行番号です。たとえば、tail -n+7 input.txt | head -1はファイルの7行目を印刷します。

tail -n+NN行から始まるすべてを表示し、head -1は1行後に停止します。


代わりのhead -N | tail -1はおそらくもう少し読みやすいです。たとえば、これは7行目を印刷します。

head -7 input.txt | tail -1

パフォーマンスに関しては、サイズが小さくなってもそれほど大きな違いはありませんが、ファイルが大きくなったときに(上から)tail | headよりも性能が優れています。

トップ投票されたsed 'NUMq;d'は知っておくと面白いですが、私はそれがhead/tailソリューションより箱から出してすぐに少ない人々に理解されるだろうそしてそれはtail/headより遅いと主張するでしょう。

私のテストでは、両方のテール/ヘッドバージョンが一貫してsed 'NUMq;d'を上回りました。それは投稿された他のベンチマークと一致しています。尾や頭が本当に悪いというケースを見つけるのは難しいです。これは驚くことではありません。これらは現代のUnixシステムで非常に最適化されると期待される操作であるからです。

パフォーマンスの違いについての考えを得るために、これらは私が巨大なファイル(9.3G)のために得る数です:

  • tail -n+N | head -1:3.7秒
  • head -N | tail -1:4.6秒
  • sed Nq;d:18.8秒

結果は異なるかもしれませんが、パフォーマンスhead | tailtail | headは一般的に小さい入力に匹敵し、そしてsedは常にかなりの要因で遅くなります(およそ5倍ほど)。

私のベンチマークを再現するには、次のことを試すことができますが、現在の作業ディレクトリに9.3Gのファイルが作成されることに注意してください。

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

これが私のマシン(SSDと16Gのメモリを搭載したThinkPad X1 Carbon)での実行結果です。最終的な実行では、すべてがディスクからではなくキャッシュから来ると思います。

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s
12
Philipp Claßen

Sed printを使って終了することもできます。

sed -n '10{p;q;}' file   # print line 10
11
bernd

これにはPerlを使うこともできます。

Perl -wnl -e '$.== NUM && print && exit;' some.file
7
Timofey Stolbov

大きなファイルのための最速の解決策は、2つの距離があるという条件で、常に末尾です。

  • ファイルの先頭から開始行まで。 Sと呼びましょう
  • 最後の行からファイルの終わりまでの距離E

知られています。そして、これを使うことができます。

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

howmanyは必要な行数だけです。

https://unix.stackexchange.com/a/216614/79743 のいくつかの詳細

6
user2350426

CaffeineConnoisseurの非常に有用なベンチマーク回答へのフォローアップとして...私は 'mapfile'メソッドが他のものと比較された速さについて興味深かったので、私は自分自身で素早い比較を試みました。私はbash 4が便利です。人々がその賞賛を歌っているので、私がそれにいた間、トップの答えの上のコメントの1つで述べられた(ヘッドよりむしろ)「テール」方法のテストを投げました。使用するテストファイルのサイズに近いサイズはありません。私がすぐに見つけることができた最高のものは14Mの血統ファイル(ちょうど12000行以下の空白で区切られた長い行)でした。

ショートバージョン:mapfileはcutメソッドよりは速いように見えますが、他のものより遅いように見えるので、私はこれをダッドと呼びます。しっぽ頭、OTOH、それが最速であるかもしれないように見えます、ファイルのこのサイズで違いはsedと比較してそれほど重要ではありません。

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time Perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

お役に立てれば!

4

上記のすべての回答が直接質問に答えます。しかし、これは直接的ではない解決策ですが、考えを引き起こすための、より重要な可能性がある考えです。

行の長さは任意なので、n行目の前のファイルのすべてのバイトを読み取る必要があります。巨大なファイルがあるか、この作業を何度も繰り返す必要があり、このプロセスに時間がかかる場合は、そもそもデータを別の方法で保存する必要があるかどうかについて真剣に考える必要があります。

本当の解決策はインデックスを持つことです。ファイルの先頭にあり、行の先頭位置を示します。データベース形式を使用することも、ファイルの先頭にテーブルを追加することもできます。あるいは、大きなテキストファイルに付随する別のインデックスファイルを作成してください。

例えば改行用の文字位置のリストを作成することができます。

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

それからtailを使って読んでください。実際にはseeksがファイルの適切な位置に直接移動します。

例えば行1000を取得します。

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • 2バイト/マルチバイト文字では動作しないかもしれません、awkは "文字認識"ですが、末尾は動作しないからです。
  • 私はこれを大きなファイルに対してテストしていません。
  • この答え も参照してください。
  • 代わりに - あなたのファイルをより小さなファイルに分割してください!
4
Sanjay Manohar

複数の行がある場合は、\ nで区切ります(通常は新しい行)。 'cut'も使えます。

echo "$data" | cut -f2 -d$'\n'

ファイルから2行目を取得します。 -f3はあなたに3行目を与えます。

3
danger89

すでにたくさんの良い答えがあります。私は個人的にはawkを使います。便宜上、bashを使用している場合は、以下を~/.bash_profileに追加するだけです。そして、次回のログイン時(またはこの更新後に.bash_profileを入力した場合)には、ファイルをパイプ処理するための新しい気の利いた "n"関数が利用可能になります。

これを実行するか、〜/ .bash_profileに入れて(bashを使用している場合)、bashを再度開いてください(またはsource ~/.bach_profileを実行してください)。

# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

それを使用するには、それを単にパイプで通します。例えば。、:

$ yes line | cat -n | nth 5 5 line

2
JJC

他の人が言ったことを使用して、私はこれが私のbashシェルの中で迅速で手軽な機能であることを望んだ。

ファイルを作成します。~/.functions

内容を追加してください。

getline() { line=$1 sed $line'q;d' $2 }

それからこれをあなたの~/.bash_profileに追加してください:

source ~/.functions

新しいbashウィンドウを開いたときには、このように関数を呼び出すだけです。

getline 441 myfile.txt

1
Mark Shust

行番号として変数を指定してsedを使用してn行目を印刷するには、次のようにします。

a=4
sed -e $a'q:d' file

ここで '-e'フラグは実行するコマンドにスクリプトを追加するためのものです。

1
aliasav

私はあなたがget.shと呼ばれるファイルに入れて/usr/local/bin/getにリンクすることができる短いbashスクリプトに上記の答えのいくつかを入れました(またはあなたが好む他の名前は何でも)。

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

それがで実行可能であることを確認してください

$ chmod +x get

PATHで利用可能にするためにそれをリンクします。

$ ln -s get.sh /usr/local/bin/get

責任を持ってお楽しみください!

P

0
polarise