それを行うための「標準的な」方法はありますか。これまではうまくいきましたhead -n | tail -1
を使ってきましたが、特にファイルから1行(またはある範囲の行)を抽出するBashツールがあるかどうか疑問に思いました。
「標準的」とは、その主な機能がそれを行っているプログラムを意味します。
巨大なファイルの場合、head
とtail
を使用したパイプ処理は遅くなります。私はsed
をこのように提案します:
sed 'NUMq;d' file
NUM
は印刷したい行の番号です。たとえば、file
の10行目を印刷するには、sed '10q;d' file
を使用します。
説明:
行番号がNUMq
の場合、NUM
は直ちに終了します。
d
はその行を表示せずに削除します。 q
は終了時にスクリプトの残りの部分をスキップさせるため、これは最後の行では禁止されています。
変数にNUM
がある場合、一重引用符の代わりに二重引用符を使用します。
sed "${NUM}q;d" file
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で行を追加するために、これをチェックすることができます。
私はこのページで提案されたソリューションをベンチマークすることができるという独特の状況を持っているので、私はこの答えをそれぞれの実行時間を含む提案されたソリューションの統合として書いています。
セットアップ
1行に1つのキーと値のペアを持つ3.261ギガバイトASCIIテキストデータファイルがあります。このファイルには合計3,339,550,320行が含まれており、私が試したVimも含めて私が試したどのエディタでも開くことができません。私が発見した値のいくつかを調査するには、このファイルをサブセット化する必要があります。
ファイルには非常に多くの行があるためです。
私の最善のシナリオは、ファイル内の他の行を読み込まずにファイルから1行だけを抽出するソリューションですが、これをBashでどのように実行するかについては考えられません。
私の正気のために私は私が私自身の問題のために必要とするであろう全500,000,000行を読むことを試みるつもりではないでしょう。代わりに、私は3,339,550,320行から50,000,000行を抽出しようとしています(つまり、全ファイルを読むのに必要な時間より60倍長い時間がかかります)。
各コマンドのベンチマークには、組み込みのtime
を使用します。
ベースライン
最初にhead
tail
ソリューションがどのようにしているのか見てみましょう:
$ 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ソリューションをテストすることができません。
結論
ほとんどの場合、head
tail
ソリューションを改善するのは難しいようです。せいぜいsed
ソリューションは効率を約3%向上させます。
(パーセンテージは式% = (runtime/baseline - 1) * 100
で計算されます)
行数50,000,000
sed
Perl
head|tail
awk
cut
行数500,000,000
sed
Perl
head|tail
awk
cut
行数3,338,559,320
sed
Perl
head|tail
awk
cut
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
うわー、すべての可能性!
これを試して:
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
が最も近くて最も使いやすいです。
# print line number 52
sed '52!d' file
この質問は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だけが組み込まれています。
私のテストによると、パフォーマンスと読みやすさの観点から、私のお勧めは次のとおりです。
tail -n+N | head -1
N
は必要な行番号です。たとえば、tail -n+7 input.txt | head -1
はファイルの7行目を印刷します。
tail -n+N
はN
行から始まるすべてを表示し、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 | tail
とtail | 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
Sed printを使って終了することもできます。
sed -n '10{p;q;}' file # print line 10
これにはPerlを使うこともできます。
Perl -wnl -e '$.== NUM && print && exit;' some.file
大きなファイルのための最速の解決策は、2つの距離があるという条件で、常に末尾です。
S
と呼びましょうE
知られています。そして、これを使うことができます。
mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"
howmanyは必要な行数だけです。
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
お役に立てれば!
上記のすべての回答が直接質問に答えます。しかし、これは直接的ではない解決策ですが、考えを引き起こすための、より重要な可能性がある考えです。
行の長さは任意なので、n行目の前のファイルのすべてのバイト は を読み取る必要があります。巨大なファイルがあるか、この作業を何度も繰り返す必要があり、このプロセスに時間がかかる場合は、そもそもデータを別の方法で保存する必要があるかどうかについて真剣に考える必要があります。
本当の解決策はインデックスを持つことです。ファイルの先頭にあり、行の先頭位置を示します。データベース形式を使用することも、ファイルの先頭にテーブルを追加することもできます。あるいは、大きなテキストファイルに付随する別のインデックスファイルを作成してください。
例えば改行用の文字位置のリストを作成することができます。
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
それからtail
を使って読んでください。実際にはseek
sがファイルの適切な位置に直接移動します。
例えば行1000を取得します。
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
複数の行がある場合は、\ nで区切ります(通常は新しい行)。 'cut'も使えます。
echo "$data" | cut -f2 -d$'\n'
ファイルから2行目を取得します。 -f3
はあなたに3行目を与えます。
すでにたくさんの良い答えがあります。私は個人的には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
他の人が言ったことを使用して、私はこれが私のbashシェルの中で迅速で手軽な機能であることを望んだ。
ファイルを作成します。~/.functions
内容を追加してください。
getline() { line=$1 sed $line'q;d' $2 }
それからこれをあなたの~/.bash_profile
に追加してください:
source ~/.functions
新しいbashウィンドウを開いたときには、このように関数を呼び出すだけです。
getline 441 myfile.txt
行番号として変数を指定してsedを使用してn行目を印刷するには、次のようにします。
a=4
sed -e $a'q:d' file
ここで '-e'フラグは実行するコマンドにスクリプトを追加するためのものです。
私はあなたが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