cat
到X
行(例如57890000到57890010)进行编码。反之亦然,即head -A /path/to/file | tail -B
或替代地
tail -C /path/to/file | head -D
其中
Y
,head
,tail
和A
从文件B
和C
的行数计算得出。但是这种方法存在两个问题: ,
D
和X
。这些命令可以相互之间比我感兴趣的行多得多(例如,如果我在一个巨大的文件中间仅读取几行),
>是否有一种方法可以使Shell正常工作并输出所需的行? (同时仅提供
Y
和A
)?#1 楼
我建议使用sed
解决方案,但是为了完整起见,awk 'NR >= 57890000 && NR <= 57890010' /path/to/file
在最后一行之后删除: >
速度测试(在macOS上,在其他系统上为YMMV):
seq 100000000 > test.in
生成的100,000,000行文件阅读行50,000,000-50,000,010
无特定顺序的测试
real
bash
内置time
报告的时间这些绝不是精确的基准,但是区别很明显且可重复,*可以很好地理解每个命令的相对速度。
*:除了前两个命令之间,
sed -n p;q
和head|tail
似乎基本相同。评论
出于好奇:您如何在两次测试之间刷新磁盘缓存?
–PawełRumian
2012年9月8日在8:08
那么tail -n +50000000 test.in |头-n10,与尾-n-50000000测试不同。 head -n10会给出正确的结果吗?
–吉尔斯'所以-不再是邪恶的'
2012年9月8日上午10:55
好的,我去做了一些基准测试。尾巴比sed快得多,区别比我预期的要大得多。
–吉尔斯'所以-不再是邪恶的'
2012年9月8日上午11:30
@吉尔斯,你是对的,我不好。 tail + | head比sed快10-15%,我添加了该基准。
–凯文
2012年9月8日13:50
我意识到这个问题要求行,但是如果您使用-c跳过字符,则tail + | head是瞬时的。当然,您不能说“ 50000000”,可能必须手动搜索要查找的部分的开头。
–丹尼·基希迈尔(Danny Kirchmeier)
14-4-25在16:26
#2 楼
如果要包含X到Y行(从1开始编号),请使用tail -n "+$X" /path/to/file | head -n "$((Y-X+1))"
tail
将读取并丢弃前X-1行(无法解决) ),然后阅读并打印以下几行。 head
将读取并打印请求的行数,然后退出。当head
退出时,tail
接收到SIGPIPE信号并死亡,因此它从输入文件中读取的缓冲区大小值(通常为几千字节)不会超过行。建议,使用sed:sed -n -e "$X,$Y p" -e "$Y q" /path/to/file
sed解决方案虽然要慢得多(至少对于GNU实用程序和Busybox实用程序而言;如果提取大部分内容,sed可能更具竞争力。文件在管道传输较慢且sed较快的OS上)。这是Linux下的快速基准测试;数据是由
seq 100000000 >/tmp/a
生成的,环境是Linux / amd64,/tmp
是tmpfs,否则计算机处于空闲状态而不进行交换。real user sys command
0.47 0.32 0.12 </tmp/a tail -n +50000001 | head -n 10 #GNU
0.86 0.64 0.21 </tmp/a tail -n +50000001 | head -n 10 #BusyBox
3.57 3.41 0.14 sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #GNU
11.91 11.68 0.14 sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #BusyBox
1.04 0.60 0.46 </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #GNU
7.12 6.58 0.55 </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #BusyBox
9.95 9.54 0.28 sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #GNU
23.76 23.13 0.31 sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #BusyBox
如果知道字节范围您想使用的位置,可以直接跳到开始位置来更快地提取它。但是对于行,您必须从头开始阅读并计算换行符。要从0开始(从x包含到y包含)提取块,块大小为b:
dd bs="$b" seek="$x" count="$((y-x))" </path/to/file
评论
您确定它们之间没有缓存吗?尾巴和sed之间的差异对我来说似乎太大了。
–PawełRumian
2012年9月8日在12:03
@gorkypl我做了几项措施,时间可比。如我所写,这都是在RAM中发生的(一切都在高速缓存中)。
–吉尔斯'所以-不再是邪恶的'
2012年9月8日12:06
@Gilles尾部将读取并丢弃第一行X-1行,这似乎是在从末尾开始给出行数时避免的,在这种情况下,根据执行时间,尾部似乎从末尾向后读取。请阅读:http://unix.stackexchange.com/a/216614/79743。
–user79743
15年7月17日在4:52
@BinaryZebra是的,如果输入是常规文件,则tail的某些实现(包括GNU tail)具有从末尾读取的试探法。改善尾巴|头解决方案相比其他方法。
–吉尔斯'所以-不再是邪恶的'
15年7月17日在7:08
#3 楼
head | tail
方法是执行此操作的最佳方法,也是最“惯用”的方法之一: /> X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"
之所以更快,是因为与
head | tail
方法相比,前X-1条线不需要穿过管道。您所说的问题有点误导人,并且可能解释了您对这种方法的一些毫无根据的疑虑。
您说您必须计算
A
,B
,C
,D
,但是如您所见,该文件的行数不需要,并且最多需要进行1次计算, shell仍然可以为您服务。您担心管道会读取过多的行。实际上,事实并非如此:就文件I / O而言,
tail | head
的效率差不多。首先,考虑所需的最少工作量:在文件中找到第X行,唯一的通用方法是读取每个字节并在计数X个换行符时停止,因为无法识别文件第X行的偏移量。到达第* X *行后,您必须阅读所有行以打印它们,并在第Y行停止。因此,任何方法都无法摆脱读取少于Y行的问题。现在,head -n $Y
读取的行数不超过Y(四舍五入到最接近的缓冲区单元,但是如果正确使用缓冲区可以提高性能,所以不必担心开销)。另外,tail
读取的内容不会超过head
,因此,我们证明了head | tail
读取的行数最少(同样,加上我们忽略的一些可忽略的缓冲)。不使用管道的单一工具方法的唯一效率优势是更少的过程(因此开销也更少)。评论
从来没有见过重定向先行。凉爽,它使管道通畅。
–clacke
16-4-27的3:42
#4 楼
最正统的方式(但不是最快的方式,如上面的Gilles所述)将使用sed
。 您的情况:
X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename
-n
选项意味着仅将相关行打印到标准输出。 >结束行号末尾的p表示在给定范围内打印行。脚本第二部分中的q通过跳过文件的其余部分来节省一些时间。
评论
我期望sed和tail |头大约相等,但事实证明尾巴头明显更快(请参阅我的答案)。
–吉尔斯'所以-不再是邪恶的'
2012年9月8日上午11:31
我不知道,从我读到的内容来看,尾部/头部被认为是更“正统”的,因为修剪文件的任一端正是它们的目的。在这些材料中,sed似乎仅在需要替换时才进入图片-在开始发生更复杂的事情时会迅速从图片中推出,因为它处理复杂任务的语法比AWK差得多过度。
– underscore_d
16-10-8在11:40
#5 楼
如果我们知道要选择的范围,则从第一行:lStart
到最后一行:lEnd
,我们可以计算:lCount="$((lEnd-lStart+1))"
如果我们知道行的总数:
lAll
我们还可以计算到文件末尾的距离:toEnd="$((lAll-lStart+1))"
然后我们将两者都知道:
"how far from the start" ($lStart) and
"how far from the end of the file" ($toEnd).
选择以下各项中的最小者:
tailnumber
,如下所示: > tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"
选择
$linestart
时,请注意附加的加号(“ +”)。请花一些额外的时间来查找。与往常一样:
tail -n"${tailnumber}" ${thefile} | head -n${lCount}
有时测得的时间是:
linesall="$(wc -l < "$thefile" )"
请注意,如果所选的行在开始或结束附近,则时间会急剧变化。在文件的一侧看似正常运行的命令,在文件的另一侧可能非常慢。
评论
评论不作进一步讨论;此对话已移至聊天。
– terdon♦
15年7月17日在22:46
@BinaryZebra-更好。
–mikeserv
15年7月19日在12:50
#6 楼
我经常这样做,所以写了这个脚本。我不需要找到行号,脚本可以完成所有操作。#!/bin/bash
# : start time
# : end time
# : log file to read
# : output file
# i.e. log_slice.sh 18:33 19:40 /var/log/my.log /var/log/myslice.log
if [[ $# != 4 ]] ; then
echo 'usage: log_slice.sh <start time> <end time> <log file> <output file>'
echo
exit;
fi
if [ ! -f ] ; then
echo "'' doesn't seem to exit."
echo 'exiting.'
exit;
fi
sline=$(grep -n " " |head -1|cut -d: -f1) #what line number is first occurrance of start time
eline=$(grep -n " " |head -1|cut -d: -f1) #what line number is first occurrance of end time
linediff="$((eline-sline))"
tail -n+${sline} |head -n$linediff >
评论
您正在回答未曾提出的问题。您的答案是10%的尾巴,这已经在问题和其他答案中进行了广泛讨论,而90%的问题是确定出现指定字符串/模式的行号,这不是问题的一部分。附言您应该始终引用您的shell参数和变量;例如“ $ 3”和“ $ 4”。
– G-Man说“恢复莫妮卡”
2014年10月8日在22:51
#7 楼
如果对数据进行分类,则需要先使用尾部然后再使用头部。cat file.name | tail -n +"3" | head -n -"1"
评论
几乎所有其他答案都至少提到了(如果不建议的话)将尾巴吹入头部。在管道中添加Acat只会增加噪音和开销,但不会增加价值。您可能会说:“如果您在键入命令时戴着手套,那么您将需要先使用尾巴然后再使用头部。” —没关系。
–斯科特
20年7月28日在5:34
这是对我有用的解决方案。
–莱里·泰勒(Lerie Taylor)
20年7月29日在20:59
您说:“这是对我有用的解决方案。” [强调]我说这是可行的解决方案。您是否尝试过tail -n +3 file.name |头-n -1?发生了什么?
–斯科特
20年7月30日,0:06
评论
仅供参考,我的答案中添加了6种方法的实际速度测试比较。另请参阅从文本文件中提取段的最佳方法是什么?
您也可以考虑使用split命令!