排序,但标题行保持在顶部


55

我从一个程序中获得输出,该程序首先产生一行,该行是一堆列标题,然后是一串数据。我想剪切此输出的各个列,并查看根据各个列排序的内容。如果没有标题,则可以通过-k选择sort与列的子集一起cutawk查看列的子集来轻松完成剪切和排序。但是,这种排序方法将列标题与其余的输出行混合在一起。有没有一种简单的方法可以将标题保留在顶部?


1
我遇到了以下链接。但是,我无法使用这种技术{ head -1; sort; }。它总是删除第一行之后的一堆文本。有谁知道为什么会这样吗?
jonderry 2011年

1
我怀疑这是因为head正在将多行读入缓冲区并将其大部分扔掉。我的sed想法有同样的问题。
安迪

@jonderry-该技术仅适用于有lseek能力的输入,因此从管道读取时将不起作用。如果您重定向到文件>outfile然后运行,它将可以正常工作{ head -n 1; sort; } <outfile
don_crissti

Answers:


58

窃取Andy的想法并使其具有功能,因此更易于使用:

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

现在我可以做:

$ ps -o pid,comm | body sort -k2
  PID COMMAND
24759 bash
31276 bash
31032 less
31177 less
31020 man
31167 man
...

$ ps -o pid,comm | body grep less
  PID COMMAND
31032 less
31177 less

ps -C COMMAND可能比更为合适grep COMMAND,但这只是一个例子。另外,-C如果您还使用了其他选择选项(例如),则无法使用-U
Mikel

或者也许应该称呼它body?如在body sort或中body grep。有什么想法吗?
Mikel

3
从改名headerbody,因为您正在对身体执行操作。希望这更有意义。
Mikel

2
记住要召集body所有随后的管道参与者:ps -o pid,comm | body grep less | body sort -k1nr
主教

1
@Tim您可以只写<foo body sort -k2body sort -k2 <foo。您想要的只是一个额外的角色。
Mikel

36

您可以使用bash这样将标题保留在顶部:

command | (read -r; printf "%s\n" "$REPLY"; sort)

或用perl来做:

command | perl -e 'print scalar (<>); print sort { ... } <>'

2
+1很棒。我认为值得捆绑作为shell函数。
Mikel

1
+1,为什么最好使用subshel​​l还是可以{}代替subshel​​l ()
jonderry 2011年

2
IFS=读取输入时禁用单词拆分。我认为阅读时没有必要$REPLYecho如果xpg_echo设置了,将扩展反斜杠转义符(不是默认值);printf在这种情况下更安全。echo $REPLY不加引号会压缩空格;我认为echo "$REPLY"应该没问题。read -r如果输入中可能包含反斜杠转义,则需要此参数。其中一些可能取决于bash版本。
安迪

1
@Andy:哇,您是对的,对于read REPLY; echo $REPLY(条带前导空格)和read; echo $REPLY(不是)有不同的规则。
Mikel

1
@Andy:IIRC,默认值xpg_echo取决于您的系统,例如在Solaris上,我认为默认值为true。这就是Gilles非常喜欢的原因printf:这是唯一行为可预测的东西。
Mikel

23

我找到了一个很好的awk版本,可以在脚本中很好地工作:

awk 'NR == 1; NR > 1 {print $0 | "sort -n"}'

1
我喜欢这个,但是需要一些解释-管道位于awk脚本中。这是如何运作的?它是在sort外部调用命令吗?有谁知道至少有一个链接指向解释awk中管道使用情况的页面?
2015年

@Wildcard您可以查看官方手册页或本入门手册。
lapo

4

骇人但有效:在排序之前,先行添加0到所有标题行和1所有其他行。排序后去除第一个字符。

… |
awk '{print (NR <= 2 ? "0 " : "1 ") $0}' |
sort -k 1 -k… |
cut -b 3-

3

这是一些不可思议的perl行噪声,您可以通过它通过输出对所有内容进行排序,但将第一行保持在顶部: perl -e 'print scalar <>, sort <>;'


2

我尝试了该command | {head -1; sort; }解决方案,并可以确认它确实搞砸了 - head从管道中读取多行,然后仅输出第一个。因此,其余head 读的输出将传递给sort--NOT其余部分从第2行开始!

结果是您丢失了命令输出开始处的行(和一个不完整的行!)(除非您仍然有第一行)-通过wc在末尾添加管道可以很容易地确认这一事实。上面的管道-但是如果您不知道这一点,将很难找到它!我花了至少20分钟的时间来弄清楚为什么在解决输出之前在输出中有部分行(前100个字节左右被截断)。

我最终做的是,它工作得很好,不需要两次运行该命令,它是:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile
sed 1d $myfile | sort

rm $myfile

如果需要将输出放入文件中,可以将其修改为:

myfile=$(mktemp)
whatever command you want to run > $myfile

head -1 $myfile > outputfile
sed 1d $myfile | sort >> outputfile

rm $myfile

您可以使用ksh93的head内置函数或line实用程序(在仍然有一个的系统上)或gnu-sed -u q或或IFS=read -r line; printf '%s\n' "$line",一次读取输入一个字节来避免这种情况。
斯特凡Chazelas

1

我认为这是最简单的。

ps -ef | ( head -n 1 ; sort )

或者这可能更快,因为它不会创建子外壳

ps -ef | { head -n 1 ; sort ; }

其他很酷的用途

标题行后的随机行

cat file.txt |  ( head -n 1 ; shuf )

标题行后的反向行

cat file.txt |  ( head -n 1 ; tac )

2
参见unix.stackexchange.com/questions/11856/…。这实际上不是一个好的解决方案。
通配符

1
不起作用,cat file | { head -n 1 ; sort ; } > file2仅显示头像
Peter Krauss

0
command | head -1; command | tail -n +2 | sort

4
这开始command两次。因此,它仅限于某些特定命令。但是,对于ps示例中的请求命令,它将起作用。
jofel 2014年

0

简单明了!

<command> | head -n 1; <command> | sed 1d | sort <....>
  • sed nd --->'n'指定行号,'d'代表删除。

1
就像一年半前约瑟夫(Jofel)对萨尔瓦(Sarva)的回答所说,这command两次开始。因此并不真正适合在管道中使用。
2015年

0

我是来这里寻找命令的解决方案的w。此命令显示谁登录的详细信息以及他们正在做什么。

为了显示排序的结果,但标题保持在顶部(标题有两行),我选择了:

w | head -n 2; w | tail -n +3 | sort

显然,这会w两次运行命令,因此可能不适用于所有情况。但是,其优点是很容易记住。

请注意,其tail -n +3意思是“显示3号以后的所有行”(man tail有关详细信息,请参见)。


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.