有没有一种“规范”的方法?我一直在使用head-n|tail-1来完成这个任务,但我一直在想是否有一个Bash工具专门从文件中提取一行(或一系列行)。
我所说的“规范”是指一个主要功能就是这样做的程序。
有没有一种“规范”的方法?我一直在使用head-n|tail-1来完成这个任务,但我一直在想是否有一个Bash工具专门从文件中提取一行(或一系列行)。
我所说的“规范”是指一个主要功能就是这样做的程序。
当前回答
您也可以使用Perl实现这一点:
perl -wnl -e '$.== NUM && print && exit;' some.file
其他回答
您也可以使用Perl实现这一点:
perl -wnl -e '$.== NUM && print && exit;' some.file
这不是一个bash解决方案,但我发现顶级选择不能满足我的需求,例如,
sed 'NUMq;d' file
速度足够快,但挂了几个小时,没有告诉任何进展。我建议编译这个cpp程序并使用它来查找所需的行。您可以使用g++main.cpp编译它,其中main.cpp是包含以下内容的文件。我得到了一个,并执行了它/a.输出
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main() {
string filename;
cout << "Enter filename ";
cin >> filename;
int needed_row_number;
cout << "Enter row number ";
cin >> needed_row_number;
int progress_line_count;
cout << "Enter at which every number of rows to monitor progress ";
cin >> progress_line_count;
char ch;
int row_counter = 1;
fstream fin(filename, fstream::in);
while (fin >> noskipws >> ch) {
int ch_int = (int) ch;
if (row_counter == needed_row_number) {
cout << ch;
}
if (ch_int == 10) {
if (row_counter == needed_row_number) {
return 0;
}
row_counter++;
if (row_counter % progress_line_count == 0) {
cout << "Progress: line " << row_counter << endl;
}
}
}
return 0;
}
UPDATE 1:在awk中找到了更快的方法
仅需5.353秒即可获得133.6 mn以上的行:
行号='133668997';(时间(pvE0<~/master_primelist_18a.txt|LC_ALL=C mawk2-F'^$'-v\_=“${rownum}”--'_{exit}!--_'))
in0: 5.45GiB 0:00:05 [1.02GiB/s] [1.02GiB/s] [======> ] 71%
( pvE 0.1 in0 < ~/master_primelist_18a.txt |
LC_ALL=C mawk2 -F'^$' -v -- ; ) 5.01s user
1.21s系统116%cpu 5.353总计
77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=
===============================================
我想质疑perl比awk更快的观点:
因此,虽然我的测试文件的行数没有那么多,但它的大小也是原来的两倍,为7.58GB-
我甚至给了perl一些内置的优势——比如行号中的硬编码,并且还排在第二位,从而从OS缓存机制中获得了任何潜在的加速(如果有的话)
f="$( grealpath -ePq ~/master_primelist_18a.txt )"
rownum='133668997'
fg;fg; pv < "${f}" | gwc -lcm
echo; sleep 2;
echo;
( time ( pv -i 0.1 -cN in0 < "${f}" |
LC_ALL=C mawk2 '_{exit}_=NR==+__' FS='^$' __="${rownum}"
) ) | mawk 'BEGIN { print } END { print _ } NR'
sleep 2
( time ( pv -i 0.1 -cN in0 < "${f}" |
LC_ALL=C perl -wnl -e '$.== 133668997 && print && exit;'
) ) | mawk 'BEGIN { print } END { print _ } NR' ;
fg: no current job
fg: no current job
7.58GiB 0:00:28 [ 275MiB/s] [============>] 100%
148,110,134 8,134,435,629 8,134,435,629 <<<< rows, chars, and bytes
count as reported by gnu-wc
in0: 5.45GiB 0:00:07 [ 701MiB/s] [=> ] 71%
( pv -i 0.1 -cN in0 < "${f}" | LC_ALL=C mawk2 '_{exit}_=NR==+__' FS='^$' ; )
6.22s user 2.56s system 110% cpu 7.966 total
77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=
in0: 5.45GiB 0:00:17 [ 328MiB/s] [=> ] 71%
( pv -i 0.1 -cN in0 < "${f}" | LC_ALL=C perl -wnl -e ; )
14.22s user 3.31s system 103% cpu 17.014 total
77.37219=195591955519519519559551=0x296B0FA7D668C4A64F7F=
我可以用perl5.36甚至perl-6重新运行测试,如果你认为这会有所不同(也没有安装)
7.966秒(mawk2)与17.014秒(perl 5.34)
在这两者之间,后者是前者的两倍多,显然哪一种在ASCII文件中获取单行数据确实更快。
This is perl 5, version 34, subversion 0 (v5.34.0) built for darwin-thread-multi-2level
Copyright 1987-2021, Larry Wall
mawk 1.9.9.6, 21 Aug 2016, Copyright Michael D. Brennan
以上所有答案都直接回答了这个问题。但这是一个不那么直接的解决方案,但可能是一个更重要的想法,可以引起人们的思考。
由于行长度是任意的,因此需要读取文件第n行之前的所有字节。如果您有一个巨大的文件或需要多次重复此任务,并且此过程非常耗时,那么您应该认真考虑是否应该首先以不同的方式存储数据。
真正的解决方案是有一个索引,例如在文件的开头,指示行开始的位置。您可以使用数据库格式,或者在文件开头添加一个表。或者,创建一个单独的索引文件,与大型文本文件一起使用。
例如,您可以为换行符创建一个字符位置列表:
awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx
然后用tail读取,它实际上直接查找文件中的适当点!
例如获得线1000:
tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
这可能不适用于2字节/多字节字符,因为awk是“字符识别”的,但tail不是。我还没有对一个大文件进行测试。另请参阅此答案。或者,将文件拆分为更小的文件!
这个问题被标记为Bash,下面是Bash(≥4)的方法:使用带有-s(跳过)和-n(计数)选项的mapfile。
如果需要获取文件文件的第42行:
mapfile -s 41 -n 1 ary < file
此时,您将得到一个数组ary,其中的字段包含文件行(包括尾部换行符),我们跳过了前41行(-s 41),并在读取一行(-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内置命令!