Unix-文件的头和尾


131

假设您有一个txt文件,同时查看文件的前10行和后10行的命令是什么?

例如,如果文件长200行,则可以一次性查看1-10行和190-200行。


您一口气是什么意思?
cnicutar 2011年

@cnicutar,即。不去头-10文件查看数据,然后分别去头-10文件并查看数据
toop

@toop如果您想要一个真实的工作示例,请参阅stackoverflow.com/a/44849814/99834
sorin

Answers:


208

您可以简单地:

(head; tail) < file.txt

如果出于某种原因需要使用管道,则如下所示:

cat file.txt | (head; tail)

注意:如果file.txt中的行数小于默认的head首行+ tail的默认行,则将打印重复的行。


54
严格来说,这不会给您原始文件的结尾,但是之后的流的结尾head已经占用了文件的前10行。(将head < file.txt; tail < file.txt其与少于20行的文件进行比较)。请记住,这只是一个非常小的要点。(但仍为+1。)
chepner 2012年

15
真好 如果要在头部和尾部之间留出空隙:(head; echo; tail)<file.txt
Simon Hibbs,2012年

3
对为什么/如何工作感到好奇。提出新问题:stackoverflow.com/questions/13718242
zellyn 2012年

9
@nametal实际上,您甚至可能收不到那么多钱。尽管head显示输入的前10行,但不能保证它不会消耗更多的行来查找第10行的结尾,而剩下的输入则更少less
chepner '16

20
很抱歉,但是答案仅在某些情况下有效。seq 100 | (head; tail)仅给我前10个数字。只有在更大的输入大小(如seq 2000)上,尾巴才会获得一些输入。
模块化的

18

ed 是个 standard text editor

$ echo -e '1+10,$-10d\n%p' | ed -s file.txt

2
如果文件多于或少于200行怎么办?而且您不知道从头开始的行数吗?
保罗

@Paul我已更改seded
kev

14

对于纯流(例如,命令的输出),可以使用“ tee”来分叉该流,并将一个流发送到头,将一个流发送到尾。这需要使用bash(+ / dev / fd / N)的'>(list)'功能:

( COMMAND | tee /dev/fd/3 | head ) 3> >( tail )

或使用/ dev / fd / N(或/ dev / stderr)以及具有复杂重定向的子shell:

( ( seq 1 100 | tee /dev/fd/2 | head 1>&3 ) 2>&1 | tail ) 3>&1
( ( seq 1 100 | tee /dev/stderr | head 1>&3 ) 2>&1 | tail ) 3>&1

(这些都不能在csh或tcsh中工作。)

对于更好控制的东西,可以使用以下perl命令:

COMMAND | perl -e 'my $size = 10; my @buf = (); while (<>) { print if $. <= $size; push(@buf, $_); if ( @buf > $size ) { shift(@buf); } } print "------\n"; print @buf;'

1
+1支持流。您可以重用stderr:COMMAND | { tee >(head >&2) | tail; } |& other_commands
jfs

2
顺便说一句,对于大于缓冲区大小(在我的系统上为8K)的文件,它会中断。cat >/dev/null修复它:COMMAND | { tee >(head >&2; cat >/dev/null) | tail; } |& other_commands
jfs 2013年

我喜欢这种解决方案,但是在玩了一段时间的aa之后,我发现在某些情况下,尾巴在头部之前运行了…… headtail命令之间没有保证的顺序:\ ...
1

7
(sed -u 10q; echo ...; tail) < file.txt

(head;tail)主题的另一个变体,但是避免了小文件的初始缓冲区填充问题。


4

head -10 file.txt; tail -10 file.txt

除此之外,您需要编写自己的程序/脚本。


1
很好,我一直使用cat和/ headtail管道传输,很高兴知道我可以单独使用它们!
保罗

然后,如何将这前10个+后10个管道传递给另一个命令?
toop

1
@保罗-与“your_program”作为WC -l它返回10,而不是20
托普

3
或者,而不必生成子壳:(需要{ head file; tail file; } | prog在花括号内有间距,并且必须在结尾加上分号)
glenn jackman 2011年

1
哇...真是太差劲了,因为在将近两年之后,他的答案与其他人的答案非常相似(但在他们之前加了时间戳),来自一个选择不发表他们为何弃权的人。真好!
2013年

4

根据JF Sebastian的评论

cat file | { tee >(head >&3; cat >/dev/null) | tail; } 3>&1

这样,您可以在一个管道中以不同方式处理第一行和其余行,这对于处理CSV数据非常有用:

{ echo N; seq 3;} | { tee >(head -n1 | sed 's/$/*2/' >&3; cat >/dev/null) | tail -n+2 | awk '{print $1*2}'; } 3>&1
N * 2
2
4
6

3

这里的问题是,面向流的程序无法事先知道文件的长度(因为如果是真正的流,可能不会有一个文件的长度)。

tail缓冲最后看到的n行之类的工具,然后等待流的结尾,然后打印。

如果要在单个命令中执行此操作(并使它具有任何偏移量,并且如果行重叠则不要重复行),则必须模仿我提到的这种行为。

试试这个awk:

awk -v offset=10 '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' yourfile

它需要更多的工作才能避免偏移量大于文件时发生的问题
Samus_ 2011年

是的,这适用于管道输出,而不仅仅是文件: a.out | awk -v ...
Camille Goudeseune

确实:),但这是awk的正常行为,大多数命令行程序在不带参数的情况下在stdin上运行。
Samus_

1
非常接近所需的行为,但似乎对于<10行,确实会添加额外的新行。
索林

3

最终花了很多时间才能解决此问题,该解决方案似乎是涵盖所有用例的唯一解决方案(到目前为止):

command | tee full.log | stdbuf -i0 -o0 -e0 awk -v offset=${MAX_LINES:-200} \
          '{
               if (NR <= offset) print;
               else {
                   a[NR] = $0;
                   delete a[NR-offset];
                   printf "." > "/dev/stderr"
                   }
           }
           END {
             print "" > "/dev/stderr";
             for(i=NR-offset+1 > offset ? NR-offset+1: offset+1 ;i<=NR;i++)
             { print a[i]}
           }'

功能列表:

  • 头的实时输出(显然不可能用于尾部)
  • 不使用外部文件
  • 进度条MAX_LINES后的每行一个点,对于长时间运行的任务非常有用。
  • stderr上的progressbar,确保进度点与head + tail分开(如果要使用管道标准输出,非常方便)
  • 避免由于缓冲(stdbuf)可能导致错误的日志记录顺序
  • 当总行数小于head + tail时,避免重复输出。

2

我一直在寻找这种解决方案一段时间。我自己用sed进行了尝试,但是事先不知道文件/流的长度的问题是无法克服的。在上面所有可用的选项中,我喜欢Camille Goudeseune的awk解决方案。他确实注意到,他的解决方案在输出中留出了多余的空白行,并带有足够小的数据集。在这里,我对他的解决方案进行了修改,删除了多余的行。

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { a_count=0; for (i in a) {a_count++}; for (i=NR-a_count+1; i<=NR; i++) print a[i] }' ; }

1

好吧,您可以随时将它们链接在一起。像这样 head fiename_foo && tail filename_foo。如果这还不够,您可以在.profile文件或您使用的任何登录文件中编写bash函数:

head_and_tail() {
    head $1 && tail $1
}

并且,稍后从您的shell提示中调用它:head_and_tail filename_foo


1

file.ext的前10行,然后是后10行:

cat file.ext | head -10 && cat file.ext | tail -10

文件的最后10行,然后是前10行:

cat file.ext | tail -10 && cat file.ext | head -10

然后,您也可以将输出通过管道传递到其他位置:

(cat file.ext | head -10 && cat file.ext | tail -10 ) | your_program


5
当您只能拨打head -10 file.txt时,为什么要使用cat?
jstarek 2011年

您可以使行数可变吗,所以调用类似于:head_ tail(foo,m,n)-返回文本的前m snd后n行?
ricardo

@ricardo,这将涉及编写一个bash脚本,该脚本需要3个args并将它们传递给tailand head或一个函数(通过对其别名)。
保罗


1

借鉴以上想法(经过bash和zsh测试)

但使用别名“帽子”的头部和尾巴

alias hat='(head -5 && echo "^^^------vvv" && tail -5) < '


hat large.sql

0

为什么不使用sed此任务?

sed -n -e 1,+9p -e 190,+9p textfile.txt


3
这适用于长度已知的文件,但不适用于长度未知的文件。
凯文(

0

要处理管道(流)以及文件,请将其添加到.bashrc或.profile文件中:

headtail() { awk -v offset="$1" '{ if (NR <= offset) print; else { a[NR] = $0; delete a[NR-offset] } } END { for (i=NR-offset+1; i<=NR; i++) print a[i] }' ; }

那你不仅可以

headtail 10 < file.txt

但是也

a.out | headtail 10

(与普通输入不同,当输入的长度超过10时,此行仍会附加虚假的空白行a.out | (head; tail)。谢谢以前的答复者。)

注意:headtail 10,不是headtail -10


0

基于@Samus_ 在此解释的有关@Aleksandra Zalcman命令的工作原理的内容,当您无法快速发现尾巴从何处开始而不计算行数时,这种变化非常方便。

{ head; echo "####################\n...\n####################"; tail; } < file.txt

或者,如果您开始使用20行以外的内容,则行数甚至可能会有所帮助。

{ head -n 18; tail -n 14; } < file.txt | cat -n

0

要打印文件的前10行和后10行,您可以尝试以下操作:

cat <(head -n10 file.txt) <(tail -n10 file.txt) | less


0
sed -n "1,10p; $(( $(wc -l ${aFile} | grep -oE "^[[:digit:]]+")-9 )),\$p" "${aFile}"

注意aFile变量包含文件的完整路径


0

我要说的是,根据文件的大小,可能不希望主动读取其内容。在这种情况下,我认为一些简单的shell脚本就足够了。

这是我最近对大量正在分析的CSV大文件进行处理的方式:

$ for file in *.csv; do echo "### ${file}" && head ${file} && echo ... && tail ${file} && echo; done

这会打印出每个文件的前10行和后10行,同时还会打印出文件名和前后的省略号。

对于单个大文件,您可以简单地运行以下命令以获得相同的效果:

$ head somefile.csv && echo ... && tail somefile.csv

0

使用stdin,但简单,可用于99%的用例

头尾

#!/usr/bin/env bash
COUNT=${1:-10}
IT=$(cat /dev/stdin)
echo "$IT" | head -n$COUNT
echo "..."
echo "$IT" | tail -n$COUNT

$ seq 100 | head_and_tail 4
1
2
3
4
...
97
98
99
100
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.