说我有一个巨大的文本文件(> 2GB),我只想对catX行(例如57890000到57890010)进行编码。反之亦然,即

head -A /path/to/file | tail -B


或替代地

tail -C /path/to/file | head -D


其中YheadtailA从文件BC的行数计算得出。

但是这种方法存在两个问题: ,DX
这些命令可以相互之间比我感兴趣的行多得多(例如,如果我在一个巨大的文件中间仅读取几行),
>是否有一种方法可以使Shell正常工作并输出所需的行? (同时仅提供YA)?

评论

仅供参考,我的答案中添加了6种方法的实际速度测试比较。

另请参阅从文本文件中提取段的最佳方法是什么?

您也可以考虑使用split命令!

#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;qhead|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条线不需要穿过管道。

您所说的问题有点误导人,并且可能解释了您对这种方法的一些毫无根据的疑虑。


您说您必须计算ABCD,但是如您所见,该文件的行数不需要,并且最多需要进行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