关于shell:如何反转文件中的行顺序?

How can I reverse the order of lines in a file?

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

所以,即从以下开始:

1
2
3
foo
bar
baz

我想结束

1
2
3
baz
bar
foo

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


另外值得一提的是:tac(哼,哼,反转cat)。 coreutils的一部分。

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

1
tac a.txt > b.txt


BSD尾巴:

1
tail -r myfile.txt

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


有着名的sed技巧:

1
2
3
4
# 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单行:

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

如果你不记得了,

1
perl -e 'print reverse <>'

在具有GNU实用程序的系统上,其他答案更简单,但并非全世界都是GNU / Linux ......


如果你碰巧在vim使用

1
:g/^/m0


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

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


1
tac <file_name>

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

在你的命令结束时:
| tac

tac完全符合您的要求,它"将每个文件写入标准输出,最后一行写入。"

tac与猫相反:-)。


请尝试以下命令:

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


Just Bash :)(4.0+)

1
2
3
4
5
6
7
8
9
10
11
function print_reversed {
    local lines i
    readarray -t lines

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

print_reversed < file


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

1
2
3
4
5
6
7
8
9
$ 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


我真的很喜欢"尾巴"答案,但我最喜欢的gawk答案是....

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


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

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

其中点被替换为反转列表的实际命令

TAC

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

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

1
2
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]))")

对于可能在shell脚本中使用tac的跨操作系统(即OSX,Linux)解决方案,使用上面提到的其他人提到的自制程序,那么就像这样使用别名:

1
2
3
4
brew install coreutils
echo"alias tac='gtac'">> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt

这适用于BSD和GNU。

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


最佳方案:

1
tail -n20 file.txt | tac


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

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

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

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

根据ephemient的答案,这几乎,但不完全,我想要的。


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


我看到很多有趣的想法。 但试试我的想法。 将文本管道输入:

rev | tr '
' '~' | rev | tr '~' '
'

假设字符'?'不在文件中。 这应该适用于每个回溯到1961年的UNIX shell。或类似的东西。


我有同样的问题,但我也希望第一行(标题)保持最佳状态。所以我需要使用awk的强大功能

1
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


您可以使用vim stdinstdout执行此操作。您还可以使用ex与POSIX兼容。 vim只是ex的可视模式。实际上,您可以将exvim -evim -e(改进的ex模式)一起使用。
vim非常有用,因为与sed之类的工具不同,它会缓存文件以进行编辑,而sed则用于流。您可能可以使用awk,但您必须手动缓冲变量中的所有内容。

我们的想法是做以下事情:

  • 从stdin读取
  • 对于每一行,将其移至第1行(反转)。命令是g/^/m0。这意味着全局,每行g;匹配行的开头,匹配任何^;在地址0之后移动它,即第1行m0
  • 打印一切。命令是%p。这意味着所有行的范围%;打印p行。
  • 强行退出而不保存文件。命令是q!。这意味着退出q;强行!
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    # 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(像sed这样的vim编辑器)来使用vim来编辑stdin。将其添加到路径中名为ved的文件中:

    1
    2
    3
    #!/usr/bin/env sh

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

    我使用一个+命令而不是+'%p' +'q!',因为vim将你限制为10个命令。因此合并它们允许"$@"具有9 +命令而不是8。

    然后你可以这样做:

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

    如果您没有vim 8,请将其放在ved中:

    1
    2
    3
    #!/usr/bin/env sh

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

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

    seq 1 20 |尾巴-r


    1
    sort -r < filename

    要么

    1
    rev < filename