如何倒转文件中行的顺序?

835

我想要反转一个文本文件(或stdin)中的行顺序,同时保留每行的内容。

也就是说,例如,从以下内容开始:

foo
bar
baz

我想最终拥有

baz
bar
foo

有没有标准的UNIX命令行实用程序可以做到这一点?


8
关于反转行的重要说明:首先要确保你的文件有一个尾随的换行符。否则,在输出文件中,输入文件的最后两行将合并为一行(至少使用 perl -e 'print reverse <>',但可能也适用于其他方法)。 - jakub.g
1
可能是如何反转文本文件的行?的重复问题。 - Greg Hewgill
这也几乎是一个重复的问题(尽管更旧),与http://unix.stackexchange.com/questions/9356/how-can-i-print-lines-from-file-backwards-without-using-tac相同。与那种情况一样,迁移到unix.stackexchange.com可能是适当的。 - mc0e
24个回答

1721

另外值得一提的是: tac (即cat的反转). 它是coreutils的一部分。

将一个文件翻转成另一个文件

tac a.txt > b.txt

80
特别提醒那些使用没有-r选项的tail版本的人!(大多数Linux用户使用GNU tail,它没有-r选项,因此我们使用GNU tac)。 - oylenshpeegul
12
仅仅是一条注释,因为有人之前提到过 tac,但是在 OS X 上似乎没有安装 tac。虽然用 Perl 编写一个替代品并不困难,但我没有真正的 tac。 - Chris Lutz
5
您可以从Fink获取适用于OS X的GNU tac。您可能还希望获取GNU tail,因为它可以执行一些BSD tail所不能的操作。 - oylenshpeegul
34
如果您使用带有Homebrew的OS X操作系统,可以使用“brew install coreutils”命令(默认安装为“gtac”)来安装"tac"。 - Robert
9
问题之一是如果文件末尾没有换行符,则前两行可能会合并为一行。echo -n "abc\ndee" > test; tac test - CMCDragonkai
显示剩余7条评论

529

144
请记住,“-r”选项不符合POSIX标准。下面的sed和awk解决方案即使在最古怪的系统中也能工作。 - guns
45
刚在Ubuntu 12.04上尝试了一下,发现我的tail版本(8.13)中没有-r选项。请改用“tac”命令代替(参见下面Mihai的回答)。 - odigity
18
勾号应该移动到下面的 tac。在 Ubuntu 12/13、Fedora 20 和 Suse 11 上,tail -r 失败了。 - rickfoosusa
9
回答应该明确提到这是仅适用于BSD系统的工具,特别是因为原帖要求一个“标准的UNIX”实用程序。这个工具不在GNU tail中,所以它甚至不是事实上的标准。 - DanC
3
对于某些人来说可能有用,但这不是针对所提出的问题的答案。也就是说,它不是一个“标准的UNIX命令行实用程序”。 - mc0e
显示剩余13条评论

188

这里有一个众所周知的sed技巧:

# reverse order of lines (emulates "tac")
# bug/feature in HHsed v1.5 causes blank lines to be deleted
sed '1!G;h;$!d'               # method 1
sed -n '1!G;h;$p'             # method 2

(解释:将非初始行添加到暂存缓冲区,交换行和暂存缓冲区,最后打印出行)

或者(执行速度更快的方式,来自于awk一行命令)from the awk one-liners:

awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file*

如果您记不住那个,
perl -e 'print reverse <>'

在使用GNU工具的系统上,其他答案更简单,但并非所有世界都是GNU/Linux...


4
来自同样的来源: awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' file*sed和awk版本在我的忙碌盒子路由器上都能工作。 'tac'和'tail -r'则不行。 - guns
8
我希望这个答案能被采纳。因为sed命令一直可用,但'tail -r'和'tac'却不是。 - ryenus
@ryenus:tac 应该能够处理不适合放在内存中的任意大文件(但行长度仍受限制)。目前还不清楚 sed 解决方案是否适用于这样的文件。 - jfs
1
更准确地说:sed 代码的时间复杂度为 O(n^2),对于大文件来说可能非常慢。因此,我赞成使用 awk 的替代方案,它是线性的。我没有尝试 perl 选项,因为它不太适合使用管道。 - Antoine Lizée
我甚至都搞不清楚怎么运行sed版本。至少我知道怎么用perlawk(还有其他答案,但我不想下载东西,而且我也不是在Linux系统上)。 - undefined
显示剩余4条评论

151

在您的命令结尾处加上:| tac

tac正好做您所要求的事情,“将每个文件按最后一行优先写入标准输出”。

tac是cat的反义词 :-).


他为什么要这样做?请解释一下tac命令的价值,这对于可能会搜索相同主题的新用户非常有用。 - Nic3500
20
这真的应该成为被采纳的答案。可惜上面的回答有这么多投票。 - joelittlejohn
4
顺便说一句:如果数据来自文件,你无需使用管道连接到 tac 命令。你可以直接使用 tac 文件名.扩展名(相当于 cat 文件名.扩展名 的反向操作)即可。 - Andreas Huttenrauch

71

如果你恰好在使用vim

:g/^/m0

Explanation来自@Ronopolis:

g表示“全局执行”。
^表示“行首”。
m表示“移动到新的行号”,0表示要移动到哪一行。
0表示“文件顶部,在当前行1之前”。
因此:“查找每一行的开头,并将其移到第0行。”

你找到第1行,将其移到顶部。 没有任何变化。 然后找到第2行并将其移动到第1行上面,即文件顶部。 现在找到第3行并将其移动到顶部。 对于每一行都重复此操作。 最后,将最后一行移至顶部。 完成后,您已经颠倒了所有行。


5
相关链接:如何颠倒行的顺序? 在 Vim SE 上。 - kenorb
4
如果您简要解释一下它的功能,我会点赞。 - mc0e
2
是的,我理解了那一部分,但我的意思是要分解vim命令的各个部分正在做什么。我现在已经查看了@kenorb链接的答案,其中提供了解释。 - mc0e
9
g代表“全局执行”。 ^代表“行首”。 m代表“移动到新的行号。” 0表示要移动到哪一行。0表示“文件顶部,在当前行1之前”。因此:“查找每一行的行首,并将其移动到第0行。” 您找到第1行,并将其移动到顶部。没有任何作用。然后找到第2行并将其移动到第1行之前,移到文件顶部。现在找到第3行并将移动到顶部。对于每一行重复此操作。最后,将最后一行移到顶部。完成后,所有行都被颠倒了。 - Ronopolis
需要注意的是,与仅使用范围相比,:g全局命令的行为方式非常特殊。例如,命令“:%m0”不会颠倒行的顺序,而“:%normal ddggP”会(以及“:g/^/normal ddggP”也会)。很棒的技巧和解释...哦对了,忘记了标记“请参阅 :help :g 以获取更多信息”... - Nathan Chappell

61
tac <file_name>

例子:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

45
$ (tac 2> /dev/null || tail -r)

尝试使用在Linux上可用的tac,如果不起作用,请使用在BSD和OSX上可用的tail -r


4
为什么不用“tac myfile.txt”呢?我错过了什么吗? - sage
8
如果tac不可用,就需要退而使用tail -rtac不符合POSIX标准,tail -r也是如此。虽然仍不完美,但这可以提高事情的成功率。 - slowpoison
我明白了 - 用于当您无法手动/交互式更改命令时。对我来说足够好了。 - sage
3
您需要一个适当的测试来检查tac是否可用。如果tac可用,但在消耗巨大的输入流的过程中耗尽了RAM和交换空间,会发生什么情况?它会失败,然后tail -r成功处理剩余的流并给出不正确的结果。 - mc0e
@PetrPeller 请参考Robert的评论上方的答案,如果您使用OSX,则可以使用Homebrew。brew install coreutils 并使用 gtac 替换 tac,如果您希望将其作为别名添加到 gtac 中,例如,如果您想要一个跨平台(Linux、OSX)使用它的shell脚本。 - lacostenycoder
在 Git Bash 中对我有效。 - MrMesees

26

请尝试以下命令:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"

不使用gawk语句,我会这样做:sed 's/^[0-9]*://g' - bng44270
2
为什么不使用“nl”而不是“grep -n”? - Good Person
3
默认情况下,nl 命令无法给空行编号。一些系统支持 -ba 选项,但不是所有系统都支持(比如 HP/UX 系统)。而 grep -n 命令则总是会给匹配的每一行加上行号,包括空行(在本例中)。 - ghoti
2
我使用 cut -d: -f2- 代替 gawk。 - Alexander Stumpf

17

仅限 Bash :) (4.0+)

function print_reversed {
    local lines i
    readarray -t lines

    for (( i = ${#lines[@]}; i--; )); do
        printf '%s\n' "${lines[i]}"
    done
}

print_reversed < file

2
+1 如果您能用 Bash 编写代码,且时间复杂度为 O(n),而且没有使用递归。(如果您做到了这些并且我能够理解,那么加 3 分) - nhed
3
尝试使用包含行“-nenenenenenene”的文件,并见证人们为什么推荐始终使用printf'%s\n'而不是echo - mtraceur
@mtraceur 我同意这一点,因为这是一个通用函数。 - konsolebox

17

对于跨操作系统(如OSX,Linux)的解决方案,可能会在shell脚本中使用tac,像其他人一样使用homebrew,然后像这样别名tac:

安装lib

对于MacOS

brew install coreutils

对于linux debian操作系统

sudo apt-get update
sudo apt-get install coreutils 

然后添加别名

echo "alias tac='gtac'" >> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接