Làm thế nào tôi có thể đảo ngược thứ tự các dòng trong một tập tin?


641

Tôi muốn đảo ngược thứ tự các dòng trong tệp văn bản (hoặc stdin), giữ nguyên nội dung của từng dòng.

Vì vậy, tức là bắt đầu bằng:

foo
bar
baz

Tôi muốn kết thúc với

baz
bar
foo

Có một tiện ích dòng lệnh UNIX tiêu chuẩn cho việc này không?


2
Lưu ý quan trọng về việc đảo ngược các dòng: trước tiên hãy đảm bảo tệp của bạn có dòng mới . Mặt khác, hai dòng cuối cùng của tệp đầu vào sẽ được hợp nhất thành một dòng trong tệp đầu ra (ít nhất là sử dụng perl -e 'print reverse <>'nhưng có lẽ nó cũng áp dụng cho các phương thức khác).
jakub.g


Cũng gần như một bản sao (mặc dù cũ hơn) của unix.stackexchange.com/questions/9356/ . Như trong trường hợp đó, di chuyển đến unix.stackexchange.com có ​​lẽ là phù hợp.
mc0e

Câu trả lời:


443

Đuôi BSD:

tail -r myfile.txt

Tham khảo: Các trang hướng dẫn FreeBSD , NetBSD , OpenBSDOS X.


120
Chỉ cần nhớ rằng tùy chọn '-r' không tuân thủ POSIX. Các giải pháp sed và awk dưới đây sẽ hoạt động ngay cả trong các hệ thống wonkiest.
súng

32
Chỉ cần thử điều này trên Ubuntu 12.04 và phát hiện ra không có tùy chọn -r cho phiên bản đuôi của tôi (8.13). Sử dụng 'tac' thay vào đó (xem câu trả lời của Mihai bên dưới).
khó chịu

12
Dấu kiểm sẽ di chuyển bên dưới để tac. đuôi -r thất bại trên Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa

3
tail -r ~ / 1 ~ tail: tùy chọn không hợp lệ - r Hãy thử 'tail --help' để biết thêm thông tin. trông giống như tùy chọn mới của nó
Bohdan

6
Câu trả lời chắc chắn nên đề cập rằng đây chỉ là BSD, đặc biệt khi OP yêu cầu tiện ích "UNIX tiêu chuẩn". Đây không phải là đuôi GNU nên nó thậm chí không phải là một tiêu chuẩn thực tế.
DanC

1401

Cũng đáng đề cập: tac(the, ahem, đảo ngược của cat). Một phần của coreutils .

Lật một tập tin vào một tập tin khác

tac a.txt > b.txt

72
Đặc biệt đáng nói với những người sử dụng phiên bản đuôi không có tùy chọn -r! (Hầu hết mọi người Linux đều có đuôi GNU, không có -r, vì vậy chúng tôi có GNU tac).
oylenshpeegul

11
Chỉ là một lưu ý, bởi vì mọi người đã đề cập đến tac trước đây, nhưng tac dường như không được cài đặt trên OS X. Không khó để viết một thay thế trong Perl, nhưng tôi không có cái thật.
Chris Lutz

5
Bạn có thể lấy GNU tac cho OS X từ Fink. Bạn cũng có thể muốn có đuôi GNU, vì nó thực hiện một số điều mà đuôi BSD không có.
oylenshpeegul

25
Nếu bạn sử dụng OS X với homebrew, bạn có thể cài đặt tac bằng cách sử dụng brew install coreutils(cài đặt gtactheo mặc định).
Robert

3
Một trong những vấn đề là nếu tệp không có dòng mới, 2 dòng đầu tiên có thể được nối thành 1 dòng. echo -n "abc\ndee" > test; tac test.
CMCDragonkai

161

Có những thủ thuật sed nổi tiếng :

# 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

(Giải thích: thêm dòng không phải ban đầu để giữ bộ đệm, dòng trao đổi và giữ bộ đệm, in dòng ở cuối)

Ngoài ra (với thực thi nhanh hơn) từ các lớp lót awk :

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

Nếu bạn không thể nhớ điều đó,

perl -e 'print reverse <>'

Trên một hệ thống có các tiện ích GNU, các câu trả lời khác đơn giản hơn, nhưng không phải tất cả thế giới là GNU / Linux ...


4
Từ cùng một nguồn: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) in tệp [j--]}' * Cả hai phiên bản sed và awk đều hoạt động trên bộ định tuyến busybox của tôi. 'tac' và 'đuôi -r' thì không.
súng

8
Tôi muốn điều này là câu trả lời được chấp nhận. coz sed luôn luôn có sẵn, nhưng không tail -rvà tac.
ryenus

@ryenus: tacdự kiến ​​sẽ xử lý các tệp lớn tùy ý không phù hợp với bộ nhớ (mặc dù độ dài dòng vẫn bị giới hạn). Không rõ liệu sedgiải pháp làm việc cho các tập tin như vậy.
jfs

Chỉ có vấn đề: hãy chuẩn bị chờ đợi :-)
Antoine Lizée

1
Chính xác hơn: mã sed nằm trong O (n ^ 2) và có thể RẤT chậm đối với các tệp lớn. Do đó upvote của tôi cho sự thay thế awk, tuyến tính. Tôi đã không thử tùy chọn perl, ít thân thiện với đường ống.
Antoine Lizée

70

ở cuối lệnh của bạn đặt: | tac

tac thực hiện chính xác những gì bạn yêu cầu, đó là "Viết từng TẬP TIN vào đầu ra tiêu chuẩn, dòng cuối cùng trước."

tac là đối diện của con mèo :-).


Tại sao anh ta nên? Vui lòng giải thích giá trị của taclệnh, điều này hữu ích cho những người dùng mới có thể cuối cùng tìm kiếm cùng một chủ đề.
Nic3500

11
Đây thực sự nên là câu trả lời được chấp nhận. Xấu hổ ở trên có rất nhiều phiếu.
joelittlejohn

62

Nếu bạn đang vimsử dụng

:g/^/m0


4
Tôi sẽ bỏ phiếu nếu bạn giải thích ngắn gọn những gì nó đã làm.
mc0e

2
Vâng, tôi nhận được bit đó, nhưng tôi có nghĩa là phá vỡ những gì các bit khác nhau của lệnh vim đang làm. Bây giờ tôi đã xem câu trả lời @kenorb được liên kết, cung cấp lời giải thích.
mc0e

5
g có nghĩa là "làm điều này trên toàn cầu. ^ có nghĩa là" bắt đầu của một dòng ". m có nghĩa là" di chuyển dòng đến một số dòng mới. 0 là dòng nào để di chuyển đến. 0 có nghĩa là "đầu tệp, trước dòng 1 hiện tại". Vì vậy: "Tìm mọi dòng bắt đầu và di chuyển nó đến dòng số 0." Bạn tìm dòng 1 và di chuyển nó lên trên cùng. Không lam gi cả. Sau đó tìm dòng 2 và di chuyển nó lên trên dòng 1, đến đầu tệp. Bây giờ tìm dòng 3 và di chuyển lên đầu. Lặp lại điều này cho mỗi dòng. Cuối cùng, bạn kết thúc bằng cách di chuyển dòng cuối cùng lên trên cùng. Khi bạn đã hoàn tất, bạn đã đảo ngược tất cả các dòng.
Ronopolis

Cần lưu ý rằng: g lệnh toàn cầu hành xử theo một cách rất riêng so với chỉ đơn giản là sử dụng phạm vi. Ví dụ: lệnh ":% m0" sẽ không đảo ngược thứ tự của các dòng, trong khi ":% ddggP bình thường" sẽ (như ý ": g / ^ / ddggP bình thường"). Thủ thuật và lời giải thích hay ... Ôi, quên mã thông báo "xem: trợ giúp: g để biết thêm thông tin" ...
Nathan Chappell

51
tac <file_name>

thí dụ:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

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

Hãy thử tac, hoạt động trên Linux và nếu nó không hoạt động tail -r, nó hoạt động trên BSD và OSX.


4
Tại sao không tac myfile.txt- tôi đang thiếu gì?
hiền nhân

8
@sage, để quay lại tail -rtrong trường hợp tackhông có sẵn. tackhông tuân thủ POSIX. Cũng không tail -r. Vẫn không thể đánh lừa, nhưng điều này giúp cải thiện tỷ lệ làm việc.
Slowpoison

Tôi thấy - ví dụ khi bạn không thể thay đổi lệnh theo cách thủ công / tương tác khi thất bại. Đủ tôt cho tôi.
hiền nhân

3
Bạn cần một bài kiểm tra thích hợp để xem tac có sẵn không. Điều gì xảy ra nếu taccó sẵn, nhưng hết RAM và trao đổi một nửa thông qua việc tiêu thụ một luồng đầu vào khổng lồ. Nó thất bại và sau đó tail -rthành công trong việc xử lý phần còn lại của luồng cho kết quả không chính xác.
mc0e

@PetrPeller Xem câu trả lời trên của Robert cho OSX sử dụng homebrew. brew install coreutils và sử dụng gtacthay thế tacvà nếu bạn thích thêm tac làm bí danh gtacnếu ví dụ bạn muốn một tập lệnh shell sử dụng nó đa nền tảng (Linux, OSX)
lacostenycoder 28/03/2017

24

Hãy thử lệnh sau:

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

thay vì tuyên bố gawk, tôi sẽ làm một cái gì đó như thế này: sed 's/^[0-9]*://g'
bng44270

2
tại sao không sử dụng "nl" thay vì grep -n?
Người tốt

3
@ GoodPerson, nltheo mặc định sẽ không đánh số dòng trống. Các -batùy chọn có sẵn trên một số hệ thống, chứ không phải là không phổ biến (HP / UX nói đến cái tâm, mặc dù tôi muốn nó sẽ không) trong khi grep -nsẽ luôn số mỗi dòng phù hợp với regex (trong trường hợp này có sản phẩm nào).
ghoti

1
Thay vì gawk tôi sử dụngcut -d: -f2-
Alexander Stumpf

17

Chỉ cần 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 cho câu trả lời trong bash và cho O (n) và không sử dụng đệ quy (+3 nếu tôi có thể)
nhed

2
Hãy thử điều này với một tệp chứa dòng -nenenenenenenevà chứng kiến ​​lý do tại sao mọi người khuyên bạn nên luôn luôn sử dụng printf '%s\n'thay vì echo.
mtraceur

@mtraceur Tôi đồng ý với điều này lần này vì đây là chức năng chung.
konsolebox

11

Phương pháp đơn giản nhất là sử dụng taclệnh. taccatnghịch đảo. Thí dụ:

$ 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
không chắc chắn tại sao câu trả lời này xuất hiện trước câu trả lời dưới đây, nhưng đó là bản sao của stackoverflow.com/a/742485/1174784 - đã được đăng từ nhiều năm trước.
Anarcat

10

Tôi thực sự thích câu trả lời " đuôi -r ", nhưng câu trả lời yêu thích của tôi là ....

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

Đã thử nghiệm với mawkUbuntu 14.04 LTS - hoạt động, vì vậy nó không phải là GNU awk cụ thể. +1
Sergiy Kolodyazhnyy

n++có thể được thay thế bằngNR
karakfa

3

EDIT sau đây tạo ra một danh sách các số được sắp xếp ngẫu nhiên từ 1 đến 10:

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

nơi các dấu chấm được thay thế bằng lệnh thực tế đảo ngược danh sách

tấc

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

python: sử dụng [:: - 1] trên sys.stdin

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

Đối với giải pháp hệ điều hành chéo (ví dụ OSX, Linux) có thể sử dụng tacbên trong tập lệnh shell, sử dụng homebrew như những người khác đã đề cập ở trên, sau đó chỉ cần bí danh như vậy:

Cài đặt lib

Dành cho MacOS

brew install coreutils

Dành cho linux debian

sudo apt-get update
sudo apt-get install coreutils 

Sau đó thêm bí danh

echo "alias tac='gtac'" >> ~/.bash_aliases (or wherever you load aliases)
source ~/.bash_aliases
tac myfile.txt

2

Điều này sẽ hoạt động trên cả BSD và GNU.

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

1

Nếu bạn muốn sửa đổi tập tin tại chỗ, bạn có thể chạy

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

Điều này loại bỏ sự cần thiết phải tạo một tệp tạm thời và sau đó xóa hoặc đổi tên ban đầu và có kết quả tương tự. Ví dụ:

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

Dựa trên câu trả lời của ephemient , đã làm gần như, nhưng không hoàn toàn, những gì tôi muốn.


1

Nó xảy ra với tôi rằng tôi muốn có được những ndòng cuối cùng của một tệp văn bản rất lớn một cách hiệu quả .

Điều đầu tiên tôi đã thử là tail -n 10000000 file.txt > ans.txt, nhưng tôi thấy nó rất chậm, vìtail phải tìm đến vị trí và sau đó di chuyển trở lại để in kết quả.

Khi tôi nhận ra nó, tôi chuyển sang một giải pháp khác : tac file.txt | head -n 10000000 > ans.txt. Lần này, vị trí tìm kiếm chỉ cần di chuyển từ cuối đến vị trí mong muốn và nó tiết kiệm 50% thời gian !

Tin nhắn về nhà:

Sử dụng tac file.txt | head -n nnếu bạn tailkhông có -rtùy chọn.


0

Giải pháp tốt nhất:

tail -n20 file.txt | tac

Chào mừng bạn đến với Stack Overflow! Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn. Xin vui lòng cố gắng không làm đông mã của bạn với các bình luận giải thích, điều này làm giảm khả năng đọc của cả mã và các giải thích!
kayess

0

Đối với người dùng Emacs: C-x h(chọn toàn bộ tệp) và sau đó M-x reverse-region. Cũng hoạt động chỉ để chọn các bộ phận hoặc các dòng và hoàn nguyên chúng.


0

Tôi thấy rất nhiều ý tưởng thú vị. Nhưng hãy thử ý tưởng của tôi. Đưa văn bản của bạn vào đây:

vòng quay | tr '\ n' '~' | vòng quay | tr '~' '\ n'

giả định rằng ký tự '~' không có trong tệp. Điều này sẽ hoạt động trên mọi shell UNIX từ năm 1961. Hoặc một cái gì đó tương tự.


-1

Tôi đã có cùng một câu hỏi, nhưng tôi cũng muốn dòng đầu tiên (tiêu đề) luôn ở trên đầu. Vì vậy, tôi cần phải sử dụng sức mạnh của 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 cũng hoạt động trong cygwin hoặc gitbash


Điều đó dường như dẫn đến kết quả 1\n20\n19...2\nhơn là 20\n19...\2\n1\n.
Đánh dấu gian hàng

-1

Bạn có thể làm điều đó với vim stdinstdout. Bạn cũng có thể sử dụng exđể tuân thủ POSIX . vimchỉ là chế độ trực quan cho ex. Trong thực tế, bạn có thể sử dụng exvới vim -ehoặc vim -E( exchế độ cải tiến ). vimlà hữu ích vì không giống như các công cụ như sednó đệm tệp để chỉnh sửa, trong khi sedđược sử dụng cho các luồng. Bạn có thể sử dụngawk , nhưng bạn sẽ phải tự đệm mọi thứ trong một biến.

Ý tưởng là để làm như sau:

  1. Đọc từ stdin
  2. Đối với mỗi dòng di chuyển nó đến dòng 1 (để đảo ngược). Lệnh là g/^/m0. Điều này có nghĩa là trên toàn cầu, cho mỗi dòng g; phù hợp với sự bắt đầu của dòng, phù hợp với bất cứ điều gì ^; di chuyển nó sau địa chỉ 0, đó là dòng 1m0 .
  3. In mọi thứ. Lệnh là %p. Điều này có nghĩa cho phạm vi của tất cả các dòng %; in dòng p.
  4. Mạnh mẽ bỏ mà không lưu tập tin. Lệnh là q!. Điều này có nghĩa là bỏ thuốc lá q; mạnh mẽ !.
# 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

Làm thế nào để làm điều này tái sử dụng

Tôi sử dụng một tập lệnh mà tôi gọi ved(vim biên tập như sed) để sử dụng vim để chỉnh sửa stdin. Thêm phần này vào một tệp được gọi vedtrong đường dẫn của bạn:

#!/usr/bin/env sh

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

Tôi đang sử dụng một +lệnh thay vì +'%p' +'q!', vì vim giới hạn bạn 10 lệnh. Vì vậy, hợp nhất chúng cho phép "$@"có 9+ lệnh thay vì 8.

Sau đó, bạn có thể làm:

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

Nếu bạn không có vim 8, vedthay vào đó , hãy đặt cái này vào :

#!/usr/bin/env sh

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

-3
rev
text here

hoặc là

rev <file>

hoặc là

rev texthere

Xin chào, chào mừng bạn đến với Stack Overflow! Khi bạn trả lời một câu hỏi, bạn nên bao gồm một số loại giải thích, như những gì tác giả đã làm sai và những gì bạn đã làm để sửa nó. Tôi nói với bạn điều này bởi vì câu trả lời của bạn đã được gắn cờ là chất lượng thấp và hiện đang được xem xét. Bạn có thể chỉnh sửa câu trả lời của mình bằng cách nhấp vào nút "Chỉnh sửa".
Federico Grandi

Đặc biệt câu trả lời mới cho câu hỏi cũ, được trả lời tốt cần có sự biện minh rộng rãi để thêm câu trả lời khác.
Gert Arnold

rev sẽ lật văn bản theo chiều ngang cũng không phải là hành vi mong muốn.
D3l_Gato

-4

đuôi -r hoạt động trong hầu hết các hệ thống Linux và MacOS

seq 1 20 | đuôi -r


-9
sort -r < filename

hoặc là

rev < filename

7
sort -rchỉ hoạt động nếu đầu vào đã được sắp xếp, đó không phải là trường hợp ở đây. revđảo ngược các ký tự trên mỗi dòng nhưng vẫn giữ nguyên thứ tự dòng đó cũng không phải là thứ mà Scotty yêu cầu. Vì vậy, câu trả lời này thực sự không có câu trả lời nào cả.
Alexander Stumpf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.