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

Posted

技术标签:

【中文标题】如何反转文件中的行顺序?【英文标题】:How can I reverse the order of lines in a file? 【发布时间】:2010-10-19 01:16:24 【问题描述】:

我想颠倒文本文件(或标准输入)中的行顺序,保留每一行的内容。

所以,即,从:

foo
bar
baz

我想结束

baz
bar
foo

是否有用于此的标准 UNIX 命令行实用程序?

【问题讨论】:

关于反转行的重要说明:首先确保您的文件有一个尾随换行符。否则,输入文件的最后两行将合并为输出文件中的一行(至少使用perl -e 'print reverse <>',但它可能也适用于其他方法)。 How to reverse lines of a text file? 的可能重复项 也几乎是 unix.stackexchange.com/questions/9356/… 的副本(尽管较旧)。在这种情况下,迁移到 unix.stackexchange.com 可能是合适的。 【参考方案1】:

还值得一提:taccat 的反面)。 coreutils的一部分。

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

tac a.txt > b.txt

【讨论】:

特别值得一提的是那些使用没有 -r 选项的 tail 版本的人! (大多数 Linux 人都有 GNU tail,它没有 -r,所以我们有 GNU tac)。 只是一个注释,因为人们之前提到过 tac,但 tac 似乎没有安装在 OS X 上。并不是说用 Perl 编写替代品很难,但我没有没有真正的。 您可以从 Fink 获得适用于 OS X 的 GNU tac。您可能还希望获得 GNU tail,因为它做了一些 BSD tail 没有做的事情。 如果您使用 OS X 和 homebrew,您可以使用 brew install coreutils 安装 tac(默认安装为 gtac)。 其中一个问题是如果文件没有尾随新行,则前 2 行可能会合并为 1 行。 echo -n "abc\ndee" > test; tac test.【参考方案2】:

BSD 尾部:

tail -r myfile.txt

参考:FreeBSD、NetBSD、OpenBSD 和 OS X 手册页。

【讨论】:

请记住,'-r' 选项不符合 POSIX 标准。下面的 sed 和 awk 解决方案即使在最不稳定的系统中也能正常工作。 刚刚在 Ubuntu 12.04 上尝试过,发现我的 tail (8.13) 版本没有 -r 选项。改用“tac”(见下面 Mihai 的回答)。 复选标记应移到 tac 下方。 tail -r 在 Ubuntu 12/13、Fedora 20、Suse 11 上失败。 tail -r ~/1 ~ tail: 无效选项 -- r 尝试使用 `tail --help' 获取更多信息。看起来像它的新选项 答案当然应该提到这仅适用于 BSD,特别是因为 OP 要求提供“标准 UNIX”实用程序。这不在 GNU tail 中,所以它甚至不是事实上的标准。【参考方案3】:

这里是well-known sed tricks:

# 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

(解释:在非起始行前添加保存缓冲区,交换行和保存缓冲区,在末尾打印行)

或者(执行速度更快)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...

【讨论】:

来自同一来源:awk 'a[i++]=$0 END for (j=i-1; j>=0;) print a[j--] ' 文件* sed 和 awk 版本都适用于我的 busybox 路由器。 'tac' 和 'tail -r' 没有。 我希望这个是公认的答案。因为 sed 始终可用,但 tail -r 和 tac 不可用。 @ryenus: tac 预计可以处理不适合内存的任意大文件(尽管行长度仍然有限​​)。尚不清楚sed 解决方案是否适用于此类文件。 唯一的问题:准备等待:-) 更准确地说:sed 代码在 O(n^2) 中,对于大文件可能非常慢。因此,我赞成 awk 替代方案,线性。我没有尝试 perl 选项,对管道不太友好。【参考方案4】:

在你的命令末尾放: | tac

tac 完全符合您的要求,它“将每个 FILE 写入标准输出,最后一行在前”。

tac 是 cat 的反义词 :-)。

【讨论】:

他为什么要这么做?请解释tac 命令的值,这对于可能最终搜索相同主题的新用户很有用。 这确实应该是公认的答案。可惜上面有这么多票。 顺便说一句:如果它来自文件,您不必通过管道传输到 tac。你可以简单地tac filename.extcat filename.ext的反面)【参考方案5】:

如果你碰巧在vim使用

:g/^/m0

【讨论】:

相关:How to reverse the order of lines? 在 Vim SE 如果你简要解释一下它的作用,我会投赞成票。 是的,我明白这一点,但我的意思是分解 vim 命令的各个部分正在做什么。我现在查看了@kenorb 链接的答案,它提供了解释。 g 表示“全局执行此操作。^ 表示“行的开头”。m 表示“将行移动到新的行号。 0 是要移动到哪一行。 0 表示“文件顶部,在当前行 1 之前”。所以:“找到每一行都有一个开头,并将其移动到第 0 行。”您找到第 1 行,并将其移至顶部。什么也没做。然后找到第 2 行并将其移到第 1 行上方,到文件的顶部。现在找到第 3 行并将 it 移到顶部。对每一行重复此操作。最后,您通过将最后一行移到顶部来完成。完成后,您已经反转了所有行。 应该注意 :g 全局命令的行为方式非常特殊,而不是简单地使用范围。例如,命令 ":%m0" 不会颠倒行的顺序,而 ":%normal ddggP" 会(就像 ":g/^/normal ddggP" 一样)。不错的技巧和解释...哦,是的,忘记了令牌“请参阅 :help :g 了解更多信息”...【参考方案6】:
tac <file_name>

示例:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

【讨论】:

【参考方案7】:
$ (tac 2> /dev/null || tail -r)

尝试tac,它适用于 Linux,如果不起作用,请使用 tail -r,它适用于 BSD 和 OSX。

【讨论】:

为什么不tac myfile.txt - 我错过了什么? @sage,在tac 不可用的情况下回退到tail -rtac 不符合 POSIX。 tail -r 也不是。仍然不是万无一失,但这提高了工作的可能性。 我明白了 - 例如,当命令失败时您无法手动/交互地更改命令。对我来说已经足够了。 您需要进行适当的测试以查看 tac 是否可用。如果tac 可用,但内存不足并在消耗大量输入流的过程中进行交换,会发生什么情况。它失败了,然后tail -r 成功处理了流的其余部分,给出了错误的结果。 @PetrPeller 请参阅 Robert 以上关于 OSX 使用自制软件的评论的回答。 brew install coreutils 并使用 gtac 代替 tac,如果您更喜欢将 tac 添加为 gtac 的别名,例如,如果您想要一个跨平台(Linux、OSX)使用它的 shell 脚本【参考方案8】:

试试下面的命令:

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

【讨论】:

而不是 gawk 声明,我会做这样的事情:sed 's/^[0-9]*://g' 为什么不用 "nl" 而不是 grep -n ? @GoodPerson, nl 默认情况下将无法为空行编号。 -ba 选项在某些系统上可用,但不是通用的(想到 HP/UX,虽然我希望它不会),而 grep -n 将始终编号 every 匹配的行(在这种情况下为空)正则表达式。 我使用 cut -d: -f2- 而不是 gawk 【参考方案9】:

只是 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

【讨论】:

+1 用于 bash 和 O(n) 中的答案以及不使用递归(如果可以的话,+3) 试试这个包含-nenenenenenene行的文件,看看为什么人们建议总是使用printf '%s\n'而不是echo @mtraceur 这次我同意这一点,因为这是一个通用函数。【参考方案10】:

对于可能在 shell 脚本中使用 tac 的跨操作系统(即 OSX、Linux)解决方案,请使用上面提到的自制软件,然后像这样使用别名 tac:

安装库

对于 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

【讨论】:

【参考方案11】:

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

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

【讨论】:

在 Ubuntu 14.04 LTS 上使用 mawk 测试 - 有效,因此它不是 GNU awk 特定的。 +1 n++ 可以替换为NR【参考方案12】:

最简单的方法是使用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 

【讨论】:

不知道为什么这个答案出现在下面的答案之前,但它是 ***.com/a/742485/1174784 的欺骗 - 这是几年前发布的。【参考方案13】:

编辑 下面生成一个随机排序的从 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]))")

【讨论】:

【参考方案14】:

如果要修改文件就地,可以运行

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

这消除了创建临时文件然后删除或重命名原始文件的需要,并且具有相同的结果。例如:

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

基于answer by ephemient,它几乎完成了我想要的,但并不完全。

【讨论】:

【参考方案15】:

这适用于 BSD 和 GNU。

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

【讨论】:

【参考方案16】:

我看到了很多有趣的想法。但是试试我的想法。将您的文本输入:

转 | tr '\n' '~' |转 | tr '~' '\n'

假定字符'~'不在文件中。这应该适用于可以追溯到 1961 年的每个 UNIX shell。或者类似的东西。

【讨论】:

谢谢,这在我的 MacOS 上效果很好。 cat foo.txt | rev | tr '\n' '~' | rev | tr '~' '\n' &gt; bar.txt【参考方案17】:

对于 Emacs 用户:C-x h(选择整个文件)然后M-x reverse-region。也适用于仅选择零件或线条并恢复它们。

【讨论】:

【参考方案18】:

我碰巧想有效地获取一个非常大的文本文件的最后n行。

我尝试的第一件事是tail -n 10000000 file.txt &gt; ans.txt,但我发现它很慢,因为tail 必须寻找到该位置,然后返回打印结果。

当我意识到这一点时,我切换到另一个解决方案:tac file.txt | head -n 10000000 &gt; ans.txt。这一次,搜索位置只需从末端移动到所需位置,节省了 50% 的时间

带回家的消息:

如果您的tail 没有-r 选项,请使用tac file.txt | head -n n

【讨论】:

【参考方案19】:

您可以在命令行上使用 Perl:

perl -e 'my @b=(); while(&lt;&gt;) push(@b, $_);; print join("", reverse(@b));' orig &gt; rev

【讨论】:

【参考方案20】:

我也有同样的问题,但我也希望第一行(标题)保持在最前面。所以我需要使用 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 看起来我有一个尾随的新行。如果你不这样做,只需修复循环i = last; i &gt;= 1; i--) 【参考方案21】:

最佳解决方案:

tail -n20 file.txt | tac

【讨论】:

欢迎来到 Stack Overflow!虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!【参考方案22】:

您可以使用 vim stdinstdout 来实现。您也可以使用ex 成为POSIX compliant。 vim 只是 ex 的可视模式。实际上,您可以将exvim -evim -E 一起使用(改进了ex 模式)。 vim 很有用,因为与 sed 之类的工具不同,它缓冲文件以供编辑,而 sed 用于流。您也许可以使用awk,但您必须手动缓冲变量中的所有内容。

这个想法是做以下事情:

    从标准输入读取 对于每一行,将其移至第 1 行(反转)。命令是g/^/m0。这意味着全局,对于每一行g;匹配行首,匹配任何^;将其移到地址 0 之后,即第 1 行 m0。 打印所有内容。命令是%p。这意味着对于所有行的范围%;打印p这一行。 强制退出而不保存文件。命令是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

【讨论】:

【参考方案23】:

tail -r 适用于大多数 Linux 和 MacOS 系统

序列 1 20 |尾 -r

【讨论】:

【参考方案24】:
rev
text here

rev <file>

rev texthere

【讨论】:

嗨,欢迎来到 Stack Overflow!当你回答一个问题时,你应该包括某种解释,比如作者做错了什么以及你做了什么来解决它。我告诉你这个是因为你的答案被标记为低质量,目前正在审查中。您可以点击“编辑”按钮edit您的答案。 特别是。旧问题的新答案需要充分的理由才能添加另一个答案。 rev 也会水平翻转文本,这不是我们想要的行为。【参考方案25】:
sort -r < filename

rev < filename

【讨论】:

sort -r 仅在输入已经排序的情况下才有效,这里不是这种情况。 rev 反转每行的字符,但保持行顺序不变,这也不是 Scotty 所要求的。所以这个答案其实根本就没有答案。

以上是关于如何反转文件中的行顺序?的主要内容,如果未能解决你的问题,请参考以下文章

Apache Spark 如何保留输出文本文件中的行顺序?

如何反转Daru :: DataFrame的行?

如何反转 JSON 多边形顺序以调整 SQL Server 中的环方向

如何转换由 | 分隔的顺序数据并且在 pyspark 中的行和列中没有换行符

MySQL - 如何更改表中的行顺序

CodeEval 挑战:输入文件中的反转字符串