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


641

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

因此,即从:

foo
bar
baz

我想结束

baz
bar
foo

为此有标准的UNIX命令行实用程序吗?


2
有关反转行的重要说明:请确保文件首先包含尾随换行符。否则,输入文件的最后两行将合并到输出文件的一行中(至少使用,perl -e 'print reverse <>'但它可能也适用于其他方法)。
jakub.g 2015年


也几乎是unix.stackexchange.com/questions/9356/…的副本(尽管更旧)。在这种情况下,迁移到unix.stackexchange.com可能是适当的。
mc0e

Answers:


444

BSD尾巴:

tail -r myfile.txt

参考:FreeBSDNetBSDOpenBSDOS X手册页。


120
只要记住'-r'选项不兼容POSIX。下面的sed和awk解决方案即使在最糟糕的系统中也可以使用。
枪声

31
刚刚在Ubuntu 12.04上尝试过,发现我的tail(8.13)版本没有-r选项。请改用“ tac”(请参见下面的Mihai答案)。
odigity 2012年

12
选中标记应移至tac下面。尾-r,粟色11.在Ubuntu 12/13,Fedora的20失败
rickfoosusa

2
tail -r〜/ 1〜tail:无效的选项-r尝试“ tail --help”以获取更多信息。看起来像是它的新选择
Bohdan 2014年

5
答案肯定应该提到这仅是BSD,特别是因为OP要求使用“标准UNIX”实用程序。这不是GNU的尾巴,所以它甚至不是事实上的标准。
DanC 2014年

1398

还值得一提:(tac,,的反向cat)。部分的coreutils

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

tac a.txt > b.txt

72
特别值得一提的是那些使用不带-r选项的tail版本的用户!(大多数Linux人士都有GNU tail,没有-r,所以我们有GNU tac)。
oylenshpeegul

11
请注意,因为人们以前曾提到过tac,但tac似乎没有安装在OS X上。并不是说用Perl编写替代品会很困难,但我没有真正的替代品。
克里斯·卢兹

5
您可以从Fink获取OS X的GNU tac。您可能还希望获得GNU尾部,因为它做了某些BSD尾部却没有的事情。
oylenshpeegul

25
如果将OS X与Homebrew一起使用,则可以使用brew install coreutilsgtac默认安装为)安装tac 。
罗伯特

3
问题之一是,如果文件没有尾随新行,则前两行可能会合并为一行。echo -n "abc\ndee" > test; tac test
CMCDragonkai

161

著名的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单行代码中(以更快的速度执行):

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;)打印a [j--]}'文件* sed和awk版本均适用我的busybox路由器。'tac'和'tail -r'没有。
枪声

8
我希望这个是可以接受的答案。coz sed始终可用,但不提供tail -rtac。
ryenus

@ryenus:tac应该处理不适合内存的任意大文件(尽管行长仍然受限制)。尚不清楚sed解决方案是否适用于此类文件。
jfs

不过唯一的问题是:准备等待:-)
AntoineLizée2014年

1
更准确地说:sed代码位于O(n ^ 2)中,对于大文件来说可能非常慢。因此,我赞成awk替代方案(线性)。我没有尝试过perl选项,对管道的友好程度较低。
AntoineLizée2014年

70

在命令末尾输入: | tac

tac完全满足您的要求,它“将每个文件写入标准输出,最后一行。”

tac与cat :-)相反。


他为什么要这样?请说明该tac命令的值,这对于可能最终搜索同一主题的新用户很有用。
Nic3500 '18

11
这确实应该是公认的答案。遗憾的是,上面有很多票。
joelittlejohn

62

如果您碰巧正在vim使用中

:g/^/m0

5
相关:如何反转行顺序?在Vim SE
kenorb

4
如果您简短地解释了它的工作,我会投票赞成。
mc0e

2
是的,我明白了这一点,但我的意思是分解一下vim命令的各个部分在做什么。我现在查看了@kenorb链接的答案,它提供了解释。
mc0e

5
g表示“全局执行此操作。^表示“一行的开始”。m表示“将该行移动到新的行号。0是要移动到的那一行。0表示“文件顶部,当前行1之前”。因此:“查找具有开头的每一行,并将其移至行号0。” 您找到第1行,并将其移到顶部。什么也没做。然后找到第2行,并将其移至第1行上方,移至文件顶部。现在找到第3行并将移到顶部。对每一行重复此操作。最后,您将最后一行移到顶部。完成后,您已经将所有行颠倒了。
Ronopolis

应该注意的是:: g全局命令的行为与仅使用范围的行为非常不同。例如,命令“:%m0”不会反转行的顺序,而“:%normal ddggP”会(与“:g / ^ / normal ddggP”一样)。不错的技巧和解释...哦,是的,忘记了令牌“有关更多信息,请参见:help:g” ...
Nathan Chappell

51
tac <file_name>

例:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

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

试试tac,它在Linux上有效,如果不起作用,请使用tail -r,它在BSD和OSX上有效。


4
为什么不tac myfile.txt-我想念什么?
圣人

8
@sage,tail -r以防万一tac无法使用。tac不符合POSIX。都不是tail -r。仍然不是万无一失,但这可以提高工作的几率。
slowpoison

我看到了-例如,当您无法手动/交互式更改命令失败时。对我来说足够好了。
Sage 2013年

3
您需要进行适当的测试,以查看tac是否可用。如果tac可用,会发生什么,但会用完RAM,并在消耗大量输入流的过程中进行交换。它失败,然后tail -r成功处理剩余的流,并给出错误的结果。
mc0e

@PetrPeller参见Robert的上述评论,以供OSX使用自制软件。brew install coreutils 并使用它gtac来代替tactac作为别名gtac(例如,如果您想要一个跨平台使用它的shell脚本(Linux,OSX))
lacostenycoder

24

尝试以下命令:

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

而不是gawk声明,我会做这样的事情: sed 's/^[0-9]*://g'
bng44270

2
为什么不使用“ nl”代替grep -n呢?
好人2012年

3
@GoodPerson,nl默认情况下将无法为空行编号。该-ba选项在某些系统上可用,不是通用的(HP / UX出现在我的脑海中,尽管我希望不是),而grep -n它将始终对与(在此例中为空)正则表达式匹配的每一行进行编号。
ghoti 2015年

1
我使用的不是gawk,而是cut -d: -f2-
亚历山大·斯特普夫

17

Just 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 2014年

2
尝试使用包含该行的文件,-nenenenenenene并见证人们建议始终使用printf '%s\n'而不是的原因echo
mtraceur

@mtraceur这次我会同意,因为这是一个常规功能。
konsolebox

11

最简单的方法是使用tac命令。taccat的逆。例:

$ cat order.txt
roger shah 
armin van buuren
fpga vhdl arduino c++ java gridgain
$ tac order.txt > inverted_file.txt
$ cat inverted_file.txt
fpga vhdl arduino c++ java gridgain
armin van buuren
roger shah 

1
不知道为什么这个答案会在下面的答案之前出现,但这是对 stackoverflow.com/a/742485/1174784的伪造 -几年前已发布。
anarcat

10

我真的很喜欢“ tail -r ”答案,但是我最喜欢的gawk答案是...。

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file

mawk在Ubuntu 14.04 LTS上进行过测试-可以正常运行,因此它不是特定于GNU AWK的。+1
Sergiy Kolodyazhnyy '16

n++可以替换为NR
karakfa,

3

编辑 以下内容将生成一个随机排序的1到10的数字列表:

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') **...**

用实际的命令替换点,从而反转列表

TAC

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(tac)

python:在sys.stdin上使用[::-1]

seq 1 10 | sort -R | tee /tmp/lst |cat <(cat /tmp/lst) <(echo '-------') \
<(python -c "import sys; print(''.join(([line for line in sys.stdin])[::-1]))")

3

对于可能使用的跨OS(即OSX,Linux)解决方案 tac在shell脚本中使用的如上面其他人所述,请使用自制程序,然后使用别名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

2

这对BSD和GNU都适用。

awk '{arr[i++]=$0} END {while (i>0) print arr[--i] }' filename

1

如果要在适当位置修改文件,可以运行

sed -i '1!G;h;$!d' filename

这样就无需创建临时文件,然后删除或重命名原始文件,并且结果相同。例如:

$tac file > file2
$sed -i '1!G;h;$!d' file
$diff file file2
$

根据ephemient回答,回答几乎完成了我想要的,但效果并不理想


1

它发生在我身上,我想最后n一个非常大的文本文件的行有效

我尝试的第一件事是tail -n 10000000 file.txt > ans.txt,但是我发现它非常慢,因为tail必须先找到该位置,然后再返回以打印结果。

当我意识到这一点时,便切换到另一种解决方案:tac file.txt | head -n 10000000 > ans.txt。这次,搜寻位置只需从末端移至所需位置,即可节省50%的时间

带回家的消息:

使用tac file.txt | head -n n如果您tail没有-r选择。


0

最佳解决方案:

tail -n20 file.txt | tac

欢迎使用Stack Overflow!尽管此代码段可以解决问题,但提供说明确实有助于提高您的帖子质量。请记住,您将来会为读者回答这个问题,而这些人可能不知道您提出代码建议的原因。也请尽量不要在代码中加入解释性注释,这会降低代码和解释的可读性!
kayess '16


0

我看到很多有趣的想法。但是尝试我的想法。将您的文本输入以下内容:

转速| tr'\ n''〜'| 转速| tr'〜''\ n'

假定字符“〜”不在文件中。这应该可以追溯到1961年的所有UNIX shell上。


-1

我有同样的问题,但我也希望第一行(标题)保持在最前面。所以我需要使用awk的力量

cat dax-weekly.csv | awk '1 { last = NR; line[last] = $0; } END { print line[1]; for (i = last; i > 1; i--) { print line[i]; } }'

PS也可以在cygwin或gitbash中使用


那似乎导致1\n20\n19...2\n而不是20\n19...\2\n1\n
Mark Booth,

-1

您可以使用vim stdin和进行操作stdout。您还可以使用ex符合POSIX标准vim只是的视觉模式ex。事实上,你可以使用ex带有vim -evim -E(改进的ex模式)。 vim之所以有用,是因为sed与之类似的工具不同,它缓冲文件以供编辑,而sed用于流。您也许可以使用awk,但是必须手动将所有内容缓冲在变量中。

该想法是要执行以下操作:

  1. 从stdin读取
  2. 对于每一行,将其移至第1行(反转)。命令是g/^/m0。这意味着对于每一行都是全局的g;匹配行的开头,匹配任何内容^;将其移到地址0(第1行)之后m0
  3. 打印所有内容。命令是%p。这意味着所有行的范围%;打印行p
  4. 强制退出而不保存文件。命令是q!。这意味着退出q; 用力地!
# Generate a newline delimited sequence of 1 to 10
$ seq 10
1
2
3
4
5
6
7
8
9
10

# Use - to read from stdin.
# vim has a delay and annoying 'Vim: Reading from stdin...' output
# if you use - to read from stdin. Use --not-a-term to hide output.
# --not-a-term requires vim 8.0.1308 (Nov 2017)
# Use -E for improved ex mode. -e would work here too since I'm not
# using any improved ex mode features.
# each of the commands I explained above are specified with a + sign
# and are run sequentially.
$ seq 10 | vim - --not-a-term -Es +'g/^/m0' +'%p' +'q!'
10
9
8
7
6
5
4
3
2
1
# non improved ex mode works here too, -e.
$ seq 10 | vim - --not-a-term -es +'g/^/m0' +'%p' +'q!'

# If you don't have --not-a-term, use /dev/stdin
seq 10 | vim -E +'g/^/m0' +'%p' +'q!' /dev/stdin

# POSIX compliant (maybe)
# POSIX compliant ex doesn't allow using + sign to specify commands.
# It also might not allow running multiple commands sequentially.
# The docs say "Implementations may support more than a single -c"
# If yours does support multiple -c
$ seq 10 | ex -c "execute -c 'g/^/m0' -c '%p' -c 'q!' /dev/stdin

# If not, you can chain them with the bar, |. This is same as shell
# piping. It's more like shell semi-colon, ;.
# The g command consumes the |, so you can use execute to prevent that.
# Not sure if execute and | is POSIX compliant.
seq 10 | ex -c "execute 'g/^/m0' | %p | q!" /dev/stdin

如何使其可重用

我使用调用的脚本ved(如vim编辑器sed)来使用vim进行编辑stdin。将此添加到ved路径中名为的文件中:

#!/usr/bin/env sh

vim - --not-a-term -Es "$@" +'%p | q!'

我使用的是一个+命令而不是+'%p' +'q!',因为vim将您限制为10个命令。因此,合并它们可以使"$@"9+命令而不是8条命令。

然后,您可以执行以下操作:

seq 10 | ved +'g/^/m0'

如果您没有vim 8,请ved改为输入:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin

-3
rev
text here

要么

rev <file>

要么

rev texthere

嗨,欢迎来到Stack Overflow!当您回答问题时,您应该提供某种解释,例如作者做错了什么以及您为解决该问题所做的工作。我之所以这么告诉您,是因为您的答案被标记为低质量,目前正在接受审核。您可以通过单击“编辑”按钮来编辑答案。
Federico Grandi

Esp。对于旧的,已经很好回答的问题的新答案需要有充分的理由来添加另一个答案。
Gert Arnold

rev也会水平翻转文本,这不是所需的行为。
D3l_Gato

-4

尾巴可在大多数Linux和MacOS系统中使用

seq 1 20 | 尾巴


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.