Bagaimana saya bisa membalik urutan baris dalam file?

641

Saya ingin membalik urutan baris dalam file teks (atau stdin), menjaga konten setiap baris.

Jadi, yaitu, dimulai dengan:

foo
bar
baz

Saya ingin berakhir dengan

baz
bar
foo

Apakah ada utilitas baris perintah UNIX standar untuk ini?

Scotty Allen
sumber
2
Catatan penting tentang membalikkan baris: pastikan file Anda memiliki trailing newline terlebih dahulu. Jika tidak, dua baris terakhir dari file input akan digabung menjadi satu baris dalam file output (setidaknya menggunakan perl -e 'print reverse <>'tetapi mungkin juga berlaku untuk metode lain).
jakub.g
kemungkinan duplikat dari Bagaimana cara membalikkan baris file teks?
Greg Hewgill
Juga hampir merupakan duplikat (meskipun lebih tua) dari unix.stackexchange.com/questions/9356/… . Seperti halnya, migrasi ke unix.stackexchange.com mungkin tepat.
mc0e

Jawaban:

444

BSD tail:

tail -r myfile.txt

Referensi: Halaman manual FreeBSD , NetBSD , OpenBSD dan OS X.

Jason Cohen
sumber
120
Ingatlah bahwa opsi '-r' tidak kompatibel dengan POSIX. Solusi sed dan awk di bawah ini akan bekerja bahkan dalam sistem yang paling keren.
senjata
32
Baru saja mencoba ini di Ubuntu 12.04, dan menemukan tidak ada opsi -r untuk versi tail saya (8.13). Gunakan 'tac' sebagai gantinya (lihat jawaban Mihai di bawah).
odigity
12
Tanda centang harus pindah ke bawah ke tac. tail -r gagal di Ubuntu 12/13, Fedora 20, Suse 11.
rickfoosusa
3
tail -r ~ / 1 ~ tail: opsi tidak valid - r Coba `tail --help 'untuk informasi lebih lanjut. terlihat seperti opsi baru
Bohdan
6
Jawabannya tentu harus menyebutkan bahwa ini hanya BSD, terutama karena OP meminta utilitas "standar UNIX". Ini bukan di GNU tail jadi itu bahkan bukan standar de facto.
DanC
1402

Juga layak disebutkan: tac(the, ahem, kebalikan dari cat). Bagian dari coreutils .

Membalik satu file ke file lain

tac a.txt > b.txt
Mihai Limbășan
sumber
72
Terutama layak disebut untuk mereka yang menggunakan versi ekor tanpa opsi -r! (Kebanyakan orang Linux memiliki ekor GNU, yang tidak memiliki -r, jadi kami memiliki GNU tac).
oylenshpeegul
11
Hanya sebuah catatan, karena orang-orang telah menyebutkan tac sebelumnya, tetapi tac tampaknya tidak diinstal pada OS X. Bukannya akan sulit untuk menulis penggantinya di Perl, tetapi saya tidak memiliki yang asli.
Chris Lutz
5
Anda bisa mendapatkan GNU tac untuk OS X dari Fink. Anda mungkin ingin mendapatkan ekor GNU juga, karena ia melakukan beberapa hal yang tidak dilakukan ekor BSD.
oylenshpeegul
25
Jika Anda menggunakan OS X dengan homebrew, Anda dapat menginstal tac using brew install coreutils(menginstal sebagai gtacdefault).
Robert
3
Salah satu masalah adalah jika file tidak memiliki baris baru yang tertinggal, 2 baris pertama dapat digabungkan sebagai 1 baris. echo -n "abc\ndee" > test; tac test.
CMCDragonkai
161

Ada trik sed yang terkenal :

# 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

(Penjelasan: tambahkan baris non-awal untuk menahan buffer, swap line and hold buffer, cetak baris di akhir)

Atau (dengan eksekusi lebih cepat) dari awk one-liners :

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

Jika Anda tidak dapat mengingatnya,

perl -e 'print reverse <>'

Pada sistem dengan utilitas GNU, jawaban lain lebih sederhana, tetapi tidak semua dunia adalah GNU / Linux ...

singkat
sumber
4
Dari sumber yang sama: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) cetak file [j--]}' * Versi sed dan awk berfungsi pada router busybox saya. 'tac' dan 'tail -r' tidak.
senjata
8
Saya berharap yang ini adalah jawaban yang diterima. karena selalu tersedia, tetapi tidak tail -rdan tidak .
ryenus
@ryenus: tacdiharapkan untuk menangani file besar sembarang yang tidak muat di memori (panjang jalur masih terbatas). Tidak jelas apakah sedsolusi berfungsi untuk file seperti itu.
jfs
Satu-satunya masalah: bersiaplah untuk menunggu :-)
Antoine Lizée
1
Lebih tepatnya: kode sed dalam O (n ^ 2), dan bisa SANGAT lambat untuk file besar. Oleh karena itu upvote saya untuk alternatif awk, linear. Saya tidak mencoba opsi perl, kurang ramah perpipaan.
Antoine Lizée
71

pada akhir perintah Anda masukkan: | tac

tac melakukan persis apa yang Anda minta, itu "Tulis setiap FILE ke output standar, baris terakhir terlebih dahulu."

tac adalah kebalikan dari kucing :-).

Yakir GIladi Edry
sumber
Kenapa harus begitu? Tolong jelaskan nilai tacperintah, ini berguna untuk pengguna baru yang mungkin akhirnya mencari subjek yang sama.
Nic3500
11
Ini benar-benar harus menjadi jawaban yang diterima. Malu di atas memiliki begitu banyak suara.
joelittlejohn
62

Jika Anda sedang vimdigunakan

:g/^/m0
DerMike
sumber
5
Terkait: Bagaimana cara membalik urutan garis? di Vim SE
kenorb
4
Saya akan memberikan suara jika Anda menjelaskan secara singkat apa yang terjadi.
mc0e
2
Ya, saya dapat bagian itu, tetapi maksud saya adalah memecah apa yang dilakukan berbagai bit perintah vim. Saya sekarang telah melihat jawaban @kenorb yang ditautkan, yang menyediakan penjelasan.
mc0e
5
g berarti "lakukan ini secara global. ^ berarti" awal garis ".m berarti" pindahkan garis ke nomor baris baru. 0 adalah baris yang akan dipindahkan. 0 berarti "di atas file, sebelum baris saat ini 1". Jadi: "Temukan setiap baris yang memiliki permulaan, dan pindahkan ke baris nomor 0." Anda menemukan baris 1, dan memindahkannya ke atas. Tidak melakukan apa-apa. Kemudian temukan baris 2 dan pindahkan di atas baris 1, ke atas file. Sekarang menemukan baris 3 dan bergerak itu ke atas. Ulangi ini untuk setiap baris. Pada akhirnya Anda selesai dengan memindahkan baris terakhir ke atas. Setelah selesai, Anda telah membalik semua baris.
Ronopolis
Perlu dicatat bahwa: perintah global berperilaku dengan cara yang sangat khusus vs hanya menggunakan rentang. Sebagai contoh, perintah ":% m0" tidak akan membalik urutan baris, sedangkan ":% normal ddggP" akan (seperti yang akan ": g / ^ / normal ddggP"). Trik dan penjelasan yang bagus ... Oh ya, lupa token "lihat: tolong: g untuk informasi lebih lanjut" ...
Nathan Chappell
51
tac <file_name>

contoh:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1
jins
sumber
42
$ (tac 2> /dev/null || tail -r)

Coba tac, yang berfungsi di Linux, dan jika itu tidak berfungsi gunakan tail -r, yang bekerja pada BSD dan OSX.

DigitalRoss
sumber
4
Kenapa tidak tac myfile.txt- apa yang saya lewatkan?
bijak
8
@sage, untuk kembali ke tail -rjaga tac- jaga jika tidak tersedia. tactidak kompatibel dengan POSIX. Tidak juga tail -r. Masih tidak aman, tetapi ini meningkatkan peluang bekerja.
slowpoison
Saya melihat - untuk contoh ketika Anda tidak dapat secara manual / interaktif mengubah perintah ketika gagal. Cukup baik untukku.
bijak
3
Anda memerlukan tes yang tepat untuk melihat apakah tac tersedia. Apa yang terjadi jika tactersedia, tetapi kehabisan RAM dan menukar setengah jalan melalui mengkonsumsi aliran input raksasa. Gagal, dan kemudian tail -rberhasil memproses sisa aliran memberikan hasil yang salah.
mc0e
@PetrPeller Lihat jawaban di atas oleh Robert untuk OSX menggunakan homebrew. brew install coreutils dan gunakan gtacdi tempat tacdan jika Anda lebih suka menambahkan tac sebagai alias untuk gtacjika misalnya Anda ingin skrip shell yang menggunakannya lintas platform (Linux, OSX)
lacostenycoder
24

Coba perintah berikut:

grep -n "" myfile.txt | sort -r -n | gawk -F : "{ print $2 }"
kenorb
sumber
alih-alih pernyataan gawk, saya akan melakukan sesuatu seperti ini: sed 's/^[0-9]*://g'
bng44270
2
mengapa tidak menggunakan "nl" bukannya grep -n?
Orang Baik
3
@GoodPerson, nlsecara default akan gagal ke nomor baris kosong. The -bapilihan tersedia pada beberapa sistem, bukan tidak universal (HP / UX datang ke pikiran, walaupun saya berharap itu tidak akan) sedangkan grep -nakan selalu jumlah setiap baris yang cocok dengan (dalam hal ini mengosongkan) regex.
ghoti
1
Alih-alih gawk saya gunakancut -d: -f2-
Alexander Stumpf
17

Just 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
konsolebox
sumber
2
+1 untuk jawaban dalam bash dan untuk O (n) dan untuk tidak menggunakan rekursi (+3 jika saya bisa)
nhed
2
Coba ini dengan file yang berisi baris -nenenenenenenedan menyaksikan alasan mengapa orang merekomendasikan selalu menggunakan printf '%s\n'bukan echo.
mtraceur
@traceur Saya setuju dengan itu karena ini adalah fungsi umum.
konsolebox
11

Metode paling sederhana adalah menggunakan tacperintah. tacadalah cat's terbalik. Contoh:

$ 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 
Yekatandilburg
sumber
1
tidak yakin mengapa jawaban ini muncul sebelum yang di bawah ini, tetapi ini merupakan penipuan dari stackoverflow.com/a/742485/1174784 - yang telah diposting tahun sebelumnya.
anarcat
10

Saya sangat suka jawaban " tail -r ", tetapi jawaban gawk favorit saya adalah ....

gawk '{ L[n++] = $0 } 
  END { while(n--) 
        print L[n] }' file
Tim Menzies
sumber
Diuji dengan mawkUbuntu 14.04 LTS - berfungsi, jadi tidak spesifik untuk GNU. +1
Sergiy Kolodyazhnyy
n++dapat diganti denganNR
karakfa
3

EDIT berikut menghasilkan daftar nomor yang diurutkan secara acak dari 1 hingga 10:

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

di mana titik-titik diganti dengan perintah aktual yang membalikkan daftar

tac

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

python: using [:: - 1] di 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]))")
Yauhen Yakimovich
sumber
3

Untuk solusi lintas OS (yaitu OSX, Linux) yang mungkin digunakan tacdi dalam skrip shell gunakan homebrew seperti yang telah disebutkan di atas, maka hanya alias tac seperti:

Instal lib

Untuk MacOS

brew install coreutils

Untuk linux debian

sudo apt-get update
sudo apt-get install coreutils 

Kemudian tambahkan alias

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

Ini akan bekerja pada BSD dan GNU.

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

Jika Anda ingin memodifikasi file di tempat, Anda dapat menjalankan

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

Ini menghilangkan kebutuhan untuk membuat file sementara dan kemudian menghapus atau mengganti nama aslinya dan memiliki hasil yang sama. Sebagai contoh:

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

Berdasarkan jawaban dengan singkat , yang melakukan hampir, tetapi tidak cukup, apa yang saya inginkan.

Mark Booth
sumber
1

Itu terjadi pada saya bahwa saya ingin mendapatkan nbaris terakhir dari file teks yang sangat besar secara efisien .

Hal pertama yang saya coba adalah tail -n 10000000 file.txt > ans.txt, tetapi ternyata sangat lambattail harus mencari ke lokasi dan kemudian bergerak kembali untuk mencetak hasilnya.

Ketika saya menyadari itu, saya beralih ke solusi lain: tac file.txt | head -n 10000000 > ans.txt. Kali ini, posisi pencarian hanya perlu bergerak dari ujung ke lokasi yang diinginkan dan menghemat waktu 50% !

Pesan rumah:

Gunakan tac file.txt | head -n njika Anda tailtidak memiliki -ropsi.

youkaichao
sumber
0

Solusi terbaik:

tail -n20 file.txt | tac
ITNM
sumber
Selamat Datang di Stack Overflow! Sementara potongan kode ini dapat menyelesaikan pertanyaan, termasuk penjelasan sangat membantu untuk meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa depan, dan orang-orang itu mungkin tidak tahu alasan untuk saran kode Anda. Tolong juga cobalah untuk tidak membuat kerumunan kode Anda dengan komentar penjelasan, ini mengurangi keterbacaan kode dan penjelasan!
kayess
0

Untuk pengguna Emacs: C-x h(pilih seluruh file) lalu M-x reverse-region. Juga berfungsi hanya untuk memilih bagian atau garis dan mengembalikannya.

Marius Hofert
sumber
0

Saya melihat banyak ide menarik. Tapi coba ide saya. Pipa teks Anda ke ini:

rev | tr '\ n' '~' | rev | tr '~' '\ n'

yang mengasumsikan bahwa karakter '~' tidak ada dalam file. Ini harus bekerja pada setiap shell UNIX kembali ke tahun 1961. Atau sesuatu seperti itu.

sopir
sumber
-1

Saya memiliki pertanyaan yang sama, tetapi saya juga ingin baris pertama (header) tetap di atas. Jadi saya perlu menggunakan kekuatan 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 juga berfungsi di cygwin atau gitbash

KIC
sumber
Itu tampaknya menghasilkan 1\n20\n19...2\ndaripada 20\n19...\2\n1\n.
Mark Booth
-1

Anda dapat melakukannya dengan vim stdindan stdout. Anda juga dapat menggunakan exuntuk menjadi POSIX compliant . vimhanyalah mode visual untuk ex. Bahkan, Anda dapat menggunakannya exdengan vim -eatau vim -E( exmode yang ditingkatkan ). vimberguna karena tidak seperti alat seperti seditu buffer file untuk diedit, sementara seddigunakan untuk stream. Anda mungkin dapat menggunakan awk, tetapi Anda harus secara manual buffer semuanya dalam suatu variabel.

Idenya adalah untuk melakukan hal berikut:

  1. Baca dari stdin
  2. Untuk setiap baris, pindahkan ke baris 1 (untuk membalikkan). Perintah adalah g/^/m0. Ini berarti secara global, untuk setiap baris g; cocok dengan awal baris, yang cocok dengan apa pun ^; pindahkan setelah alamat 0, yaitu baris 1m0 .
  3. Cetak semuanya. Perintah adalah %p. Ini berarti untuk rentang semua lini %; cetak garisp .
  4. Berhenti paksa tanpa menyimpan file. Perintah adalah q!. Ini berarti berhenti q; dengan paksa !.
# 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

Cara membuat ini dapat digunakan kembali

Saya menggunakan skrip yang saya panggil ved(vim editor like sed) untuk menggunakan vim untuk mengedit stdin. Tambahkan ini ke file yang disebut veddi jalur Anda:

#!/usr/bin/env sh

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

Saya menggunakan satu +perintah, bukan +'%p' +'q!', karena vim membatasi Anda hingga 10 perintah. Jadi menggabungkan mereka memungkinkan "$@"untuk memiliki 9+ perintah, bukan 8.

Maka Anda dapat melakukan:

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

Jika Anda tidak memiliki vim 8, masukkan ini vedsebagai gantinya:

#!/usr/bin/env sh

vim -E "$@" +'%p | q!' /dev/stdin
dosentmatter
sumber
-3
rev
text here

atau

rev <file>

atau

rev texthere
pengguna13575069
sumber
Hai, selamat datang di Stack Overflow! Ketika Anda menjawab pertanyaan, Anda harus memasukkan semacam penjelasan, seperti apa yang salah penulis dan apa yang Anda lakukan untuk memperbaikinya. Saya memberi tahu Anda ini karena jawaban Anda telah ditandai sebagai berkualitas rendah dan saat ini sedang ditinjau. Anda dapat mengedit jawaban Anda dengan mengeklik tombol "Edit".
Federico Grandi
Esp. jawaban baru untuk pertanyaan lama, yang dijawab dengan baik membutuhkan banyak pembenaran untuk menambahkan jawaban lain.
Gert Arnold
rev akan membalik teks secara horizontal juga yang bukan perilaku yang diinginkan.
D3l_Gato
-4

ekor -r bekerja di sebagian besar sistem Linux dan MacOS

seq 1 20 | ekor -r

Bohdan
sumber
-9
sort -r < filename

atau

rev < filename
Yoker Rekoy
sumber
7
sort -rhanya berfungsi jika input sudah diurutkan, yang tidak terjadi di sini. revmembalikkan karakter per baris tetapi menjaga urutan garis tetap utuh yang juga bukan apa yang diminta Scotty. Jadi jawaban ini sebenarnya tidak ada jawaban sama sekali.
Alexander Stumpf