如何反转文件中的行顺序?
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】:
还值得一提:tac
(cat
的反面)。 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.ext
(cat 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 -r
。 tac
不符合 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
命令。 tac
是 cat
的倒数。
示例:
$ 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' > bar.txt
【参考方案17】:
对于 Emacs 用户:C-x h
(选择整个文件)然后M-x reverse-region
。也适用于仅选择零件或线条并恢复它们。
【讨论】:
【参考方案18】:我碰巧想有效地获取一个非常大的文本文件的最后n
行。
我尝试的第一件事是tail -n 10000000 file.txt > ans.txt
,但我发现它很慢,因为tail
必须寻找到该位置,然后返回打印结果。
当我意识到这一点时,我切换到另一个解决方案:tac file.txt | head -n 10000000 > ans.txt
。这一次,搜索位置只需从末端移动到所需位置,节省了 50% 的时间!
带回家的消息:
如果您的tail
没有-r
选项,请使用tac file.txt | head -n n
。
【讨论】:
【参考方案19】:您可以在命令行上使用 Perl:
perl -e 'my @b=(); while(<>) push(@b, $_);; print join("", reverse(@b));' orig > 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 >= 1; i--)
【参考方案21】:
最佳解决方案:
tail -n20 file.txt | tac
【讨论】:
欢迎来到 Stack Overflow!虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!【参考方案22】:您可以使用 vim
stdin
和 stdout
来实现。您也可以使用ex
成为POSIX compliant。 vim
只是 ex
的可视模式。实际上,您可以将ex
与vim -e
或vim -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 所要求的。所以这个答案其实根本就没有答案。以上是关于如何反转文件中的行顺序?的主要内容,如果未能解决你的问题,请参考以下文章
如何反转 JSON 多边形顺序以调整 SQL Server 中的环方向