【问题标题】: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 <>',但它可能也适用于其他方法)。
  • 也几乎是 unix.stackexchange.com/questions/9356/… 的副本(尽管较旧)。在这种情况下,迁移到 unix.stackexchange.com 可能是合适的。

标签: shell unix command-line


【解决方案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

参考:FreeBSDNetBSDOpenBSDOS 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

【讨论】:

  • 如果你简要解释一下它的作用,我会投赞成票。
  • 是的,我明白这一点,但我的意思是分解 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 
      

      【讨论】:

      【解决方案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 compliantvim 只是 ex 的可视模式。实际上,您可以将exvim -evim -E 一起使用(改进了ex 模式)。 vim 很有用,因为与 sed 之类的工具不同,它缓冲文件以供编辑,而 sed 用于流。您也许可以使用awk,但您必须手动缓冲变量中的所有内容。

                  这个想法是做以下事情:

                  1. 从标准输入读取
                  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
                  

                  【讨论】:

                    【解决方案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 所要求的。所以这个答案其实根本就没有答案。
                      猜你喜欢
                      • 2010-11-16
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2016-05-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多