Bir dosyadaki satırların sırasını nasıl tersine çevirebilirim?


641

Her satırın içeriğini koruyarak, bir metin dosyasındaki (veya stdin) satırların sırasını tersine çevirmek istiyorum.

Yani, yani:

foo
bar
baz

Sonunda istiyorum

baz
bar
foo

Bunun için standart bir UNIX komut satırı yardımcı programı var mı?


2
Satırları tersine çevirme hakkında önemli not: Dosyanızın önce bir satırsonu olduğundan emin olun . Aksi takdirde, bir giriş dosyasının son iki satırı, bir çıkış dosyasındaki bir satıra birleştirilir (en azından kullanılarak, perl -e 'print reverse <>'ancak muhtemelen diğer yöntemler için de geçerlidir).
jakub.g


Ayrıca unix.stackexchange.com/questions/9356/… 'nin neredeyse bir kopyası (daha eski olsa da) . Bu durumda olduğu gibi, unix.stackexchange.com'a geçiş muhtemelen uygun olacaktır.
mc0e

Yanıtlar:


444

BSD kuyruğu:

tail -r myfile.txt

Referans: FreeBSD , NetBSD , OpenBSD ve OS X kılavuz sayfaları.


120
'-R' seçeneğinin POSIX uyumlu olmadığını unutmayın. Aşağıdaki sed ve awk çözümleri en zorlu sistemlerde bile çalışacaktır.
silahlar

31
Sadece Ubuntu 12.04'te denedim ve kuyruk versiyonum için 8.1 seçeneği yok. Bunun yerine 'tac' kullanın (aşağıdaki Mihai'nin cevabına bakın).
odigity

12
Onay işareti tac için aşağı doğru hareket etmelidir. kuyruk -r Ubuntu 12/13, Fedora 20, Suse 11'de başarısız oluyor.
rickfoosusa

2
tail -r ~ / 1 ~ tail: geçersiz seçenek - r Daha fazla bilgi için `tail --help 'komutunu deneyin. yeni seçeneğine benziyor
Bohdan

5
Cevap, özellikle OP'nin "standart UNIX" yardımcı programı istediğinden, bunun yalnızca BSD olduğunu kesinlikle belirtmelidir. Bu GNU kuyruğunda değil, fiili bir standart bile değil.
DanC

1399

Ayrıca bahsetmeye değer: tac(ahem, tersi cat). Coreutils'in bir parçası .

Bir dosyayı diğerine çevirme

tac a.txt > b.txt

72
Özellikle -r seçeneği olmayan kuyruk sürümünü kullananlara bahsetmeye değer! (Çoğu Linux çalışanı -r içermeyen GNU kuyruğuna sahiptir, bu yüzden GNU tac'a sahibiz).
oylenshpeegul

11
Sadece bir not, çünkü insanlar daha önce tac'den bahsetti, ancak tac OS X'e yüklenmiş gibi görünmüyor. Perl'de bir yedek yazmak zor olmayacak, ama gerçek olana sahip değilim.
Chris Lutz

5
Fink'ten OS X için GNU tac alabilirsiniz. BSD kuyruğunun yapmadığı bazı şeyleri yaptığı için GNU kuyruğu da almak isteyebilirsiniz.
oylenshpeegul

25
Homebrew ile OS X kullanıyorsanız, tac'yi brew install coreutils( gtacvarsayılan olarak kurulumlar ) kullanarak kurabilirsiniz .
Robert

3
Sorunlardan biri, dosyanın sondaki yeni satırı yoksa, ilk 2 satır 1 satır olarak birleştirilebilir. echo -n "abc\ndee" > test; tac test.
CMCDragonkai

161

Orada tanınmış sed hileler :

# 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

(Açıklama: arabelleği tutmak için başlangıçta olmayan satırın başına ekle, hattı değiştir ve arabayı beklet, sonunda satırda yazdır)

Alternatif olarak (daha hızlı uygulama ile) awk tekli gömleklerden :

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

Bunu hatırlayamıyorsanız,

perl -e 'print reverse <>'

GNU yardımcı programlarına sahip bir sistemde, diğer yanıtlar daha basittir, ancak tüm dünya GNU / Linux değildir ...


4
Aynı kaynaktan: awk '{a [i ++] = $ 0} END {for (j = i-1; j> = 0;) bir [j--]}' dosyası yazdırın * Hem sed hem de awk sürümleri benim meşgul kutusu yönlendiricim. 'tac' ve 'tail -r' yapmaz.
guns

8
Keşke kabul edilen cevap budur. coz sed her zaman kullanılabilir, ancak değil tail -rve tac.
ryenus

@ryenus: tacbelleğe sığmayan keyfi büyük dosyaları işlemesi beklenir (satır uzunluğu yine de sınırlıdır). sedÇözümün bu tür dosyalar için çalışıp çalışmadığı belirsizdir .
jfs

Tek sorun olsa: beklemeye hazırlıklı olun :-)
Antoine Lizée

1
Daha doğrusu: sed kodu O (n ^ 2) biçimindedir ve büyük dosyalar için ÇOK yavaş olabilir. Bu yüzden awk alternatifi için doğrusal oyum. Perl seçeneği, daha az boru dostu deneyin vermedi.
Antoine Lizée

70

komutunuzun sonuna şunu koyun: | tac

tac tam olarak istediğini yapar, "Her bir DOSYAYI standart çıktıya yaz, önce son satır."

tac kedinin tam tersidir :-).


Neden ki? Lütfen tackomutun değerini açıklayın , bu aynı konuyu arayabilecek yeni kullanıcılar için yararlıdır.
Nic3500

11
Bu gerçekten kabul edilen cevap olmalı. Utanç yukarıda çok oy var.
joelittlejohn

62

İçinde olmak olur vimkullanılmak

:g/^/m0


4
Ne yaptığını kısaca açıkladıysan, oy verirdim.
mc0e

2
Evet, biraz olsun, ama vim komutunun çeşitli parçalarının ne yaptığını kırmak istedim. Şimdi açıklama sağlayan @kenorb bağlantılı cevabına baktım.
mc0e

5
g "bunu global olarak yapın. ^" bir satırın başlangıcı "anlamına gelir. m," satırı yeni bir satır numarasına taşımak anlamına gelir. 0, hangi satıra taşınacağıdır. 0, "geçerli satır 1'den önce dosyanın üstü" anlamına gelir. Yani: "Başlangıcı olan her satırı bulun ve 0 numaralı satıra taşıyın." 1. satırı bulursunuz ve en üste taşırsınız. Hiç birşey yapmıyor. Sonra 2. satırı bulun ve 1. satırın üstüne, dosyanın üstüne taşıyın. Şimdi çizgiyi 3 bulup taşımak o üstüne. Bunu her satır için tekrarlayın. Sonunda son satırı yukarı taşıyarak bitirirsiniz. İşiniz bittiğinde, tüm satırları ters çevirdiniz.
Ronopolis

Şunu belirtmek gerekir ki: g global komutu, sadece aralıkları kullanarak çok özel bir şekilde davranır. Örneğin, ":% m0" komutu satırların sırasını tersine çevirmez, ":% normal ddggP" ise ("olduğu gibi:: g / ^ / normal ddggP"). Güzel hile ve açıklama ... Ah evet, jetonu unuttum "bkz: yardım: daha fazla bilgi için g" ...
Nathan Chappell

51
tac <file_name>

misal:

$ cat file1.txt
1
2
3
4
5

$ tac file1.txt
5
4
3
2
1

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

tacLinux üzerinde çalışan ve bu işe yaramazsa tail -rBSD ve OSX üzerinde çalışan deneyin .


4
Neden olmasın tac myfile.txt- neyi özlüyorum?
adaçayı

8
@sage, geri düşmesi tail -rdurumunda tacmevcut değildir. tacPOSIX uyumlu değil. İkisi de değil tail -r. Hala kusursuz değil, ama bu işlerin çalışma olasılığını artırır.
slowpoison

Görüyorum - komutu başarısız olduğunda manuel / etkileşimli olarak değiştiremediğiniz durumlar için. Benim için yeterince iyi.
adaçayı

3
Tac'in kullanılabilir olup olmadığını görmek için uygun bir teste ihtiyacınız vardır. Varsa ne olur tac, ancak RAM biterse ve dev bir giriş akışı tüketerek yarıya kadar değiştirin. Başarısız olur ve daha sonra tail -ryanlış bir sonuç vererek akışın geri kalanını işlemeyi başarır.
mc0e

@PetrPeller Robert tarafından OSX kullanımı homebrew için yukarıdaki cevaba bakınız. yerine brew install coreutils kullanın ve tercihiniz takma ad olarak ekleyin ve örneğin çapraz platform (Linux, OSX) kullanan bir kabuk betiği istiyorsanızgtactacgtac
lacostenycoder 28:17

24

Aşağıdaki komutu deneyin:

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

gawk ifadesi yerine, böyle bir şey sed 's/^[0-9]*://g'
yapardım

2
neden grep -n yerine "nl" kullanmıyorsunuz?
İyi Kişi

3
@ GoodPerson, nlvarsayılan olarak boş satırları numaralandıramaz. Bu -baseçenek bazı sistemlerde mevcuttur, evrensel değildir (HP / UX akla gelir, ancak istemesem de), oysa grep -nher zaman (bu durumda boş) normal ifadeyle eşleşen her satırı numaralar .
ghoti

1
Gawk yerine kullanıyorumcut -d: -f2-
Alexander Stumpf

17

Sadece 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
Bash ve O (n) için cevap için ve özyineleme kullanmama için +1 (yapabiliyorsam +3)
nhed

2
Bunu satırı içeren bir dosyayla deneyin -neneneneneneneve insanların neden her zaman printf '%s\n'yerine kullanmasını önerdiğine tanık olun echo.
mtraceur

@mtraceur Bu kez genel bir işlev olduğu için buna katılıyorum.
konsolebox

11

En basit yöntem tackomutu kullanmaktır . tacolan catbireyin ters. Misal:

$ 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
bu cevabın neden aşağıdaki yanıttan önce göründüğünden emin değilim, ancak yıllar önce yayınlanan stackoverflow.com/a/742485/1174784'ün bir kopyası .
anarcat

10

Gerçekten " kuyruk -r " yanıtı gibi, ama benim en sevdiğim gawk cevap ....

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

mawkUbuntu 14.04 LTS ile test edilmiştir - çalışır, bu nedenle GNU awk'ye özgü değildir. +1
Sergiy Kolodyazhnyy

n++ile değiştirilebilirNR
karakfa

3

Aşağıdakileri DÜZENLE , 1'den 10'a kadar rastgele sıralı bir sayı listesi oluşturur:

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

noktaların yerini, listeyi tersine çeviren gerçek komutla değiştirir

tac

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

python: sys.stdin'de [:: - 1] kullanımı

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

Kullanabilecek çapraz OS (yani OSX, Linux) çözümü için tacBir kabuk komut dosyasının içinde yukarıda belirtildiği gibi homebrew kullanın, sonra sadece takma ad bu şekilde:

Lib yükle

MacOS için

brew install coreutils

Linux debian için

sudo apt-get update
sudo apt-get install coreutils 

Ardından takma ad ekleyin

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

2

Bu hem BSD hem de GNU üzerinde çalışacaktır.

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

1

Dosyayı yerinde değiştirmek istiyorsanız,

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

Bu, geçici bir dosya oluşturma gereğini ortadan kaldırır ve ardından orijinali siler veya yeniden adlandırır ve aynı sonuca sahiptir. Örneğin:

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

İstediğim neredeyse, ama tam olarak değil, ephemient'in cevabına dayanarak .


1

Bana nçok büyük bir metin dosyasının son satırlarını verimli bir şekilde almak istiyorum .

Denediğim ilk şey tail -n 10000000 file.txt > ans.txt, ama çok yavaş buldum, çünkü tailkonumu aramak zorunda ve sonra sonuçları yazdırmak için geri hareket ediyor.

Ben bunu fark, ben başka bir çözüme geçiş: tac file.txt | head -n 10000000 > ans.txt. Bu kez, arama pozisyonunun sadece sondan istenen yere gitmesi gerekiyor ve % 50 zaman tasarrufu sağlıyor !

Eve mesaj atın:

Kullan tac file.txt | head -n nsenin eğer tailyok -rseçeneği.


0

En iyi çözüm:

tail -n20 file.txt | tac

Stack Overflow'a hoş geldiniz! Bu kod snippet'i soruyu çözebilir, ancak bir açıklama dahil olmak , yayınınızın kalitesini artırmaya yardımcı olur. Gelecekte okuyucular için soruyu cevapladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceğini unutmayın. Lütfen kodunuzu açıklayıcı yorumlarla doldurmamaya çalışın, bu hem kodun hem de açıklamaların okunabilirliğini azaltır!
kayess

0

Emacs kullanıcıları için: C-x h(tüm dosyayı seçin) ve ardından M-x reverse-region. Ayrıca yalnızca parçaları veya çizgileri seçmek ve bunları geri almak için çalışır.


0

Çok ilginç fikirler görüyorum. Ama fikrimi dene. Metninizi buna ekleyin:

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

ki bu da '~' karakterinin dosyada olmadığını varsayar. Bu, 1961 yılına kadar her UNIX kabuğunda işe yarayacaktır.


-1

Aynı soruyu sordum, ama ilk satırın da (başlık) üstte kalmasını istedim. Bu yüzden awk gücünü kullanmalıydım

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 ayrıca cygwin veya gitbash'ta da çalışır


Bunun sonucu 1\n20\n19...2\ndeğil gibi görünüyor 20\n19...\2\n1\n.
Mark Booth

-1

Bunu vim stdinve ile yapabilirsiniz stdout. POSIX uyumluex olmak için de kullanabilirsiniz . sadece görsel modudur . Aslında, veya (gelişmiş mod) ile kullanabilirsiniz . yararlıdır, çünkü bu gibi araçların aksine dosyayı düzenleme için arabelleğe alırken , akışlar için kullanılır. Kullanabilirsinizvimexexvim -evim -Eexvimsedsedawk , ancak bir değişkendeki her şeyi manuel olarak arabelleğe almanız gerekir.

Fikir aşağıdakileri yapmaktır:

  1. Stdin'den okuyun
  2. Her satır için satır 1'e (tersine) hareket ettirin. Komut g/^/m0. Bu, küresel olarak, her satır için g; herhangi bir şeyle eşleşen çizginin başlangıcıyla eşleşir ^; 1. adres olan 0 adresinden sonra taşıyın m0.
  3. Her şeyi yazdırın. Komut %p. Bu, tüm hatların aralığı için anlamına gelir %; çizgiyi yazdırın p.
  4. Dosyayı kaydetmeden zorla çıkın. Komut q!. Bu, bırakma anlamına gelir q; zorla !.
# 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

Bu nasıl yeniden kullanılabilir hale getirilir

Düzenlemek için vim kullanmak için çağırdığım bir komut dosyası ved(vim editörü gibi sed) kullanıyorum stdin. Bunu yolunuzda adlandırılan bir dosyaya vedekleyin:

#!/usr/bin/env sh

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

Bunun +yerine bir komut kullanıyorum +'%p' +'q!', çünkü vim sizi 10 komutla sınırlandırıyor. Böylece onları birleştirmek "$@"9+ , 8 yerine komutuna .

Sonra şunları yapabilirsiniz:

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

Eğer vim 8'iniz yoksa, bunu vedyerine koyun :

#!/usr/bin/env sh

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

-3
rev
text here

veya

rev <file>

veya

rev texthere

Merhaba, Stack Overflow'a hoş geldiniz! Bir soruyu cevapladığınızda, yazarın neyi yanlış yaptığı ve düzeltmek için ne yaptığınız gibi bir tür açıklama eklemelisiniz. Size söylüyorum çünkü cevabınız düşük kaliteli olarak işaretlenmiş ve şu anda inceleniyor. Sen olabilir düzenleme "Düzenle" düğmesini tıklayarak cevabınızı.
Federico Grandi

Esp. eski, iyi cevaplanmış sorulara yeni cevaplar, başka bir cevap eklemek için yeterli gerekçeye ihtiyaç duyar.
Gert Arnold

rev, metni yatay olarak da çevirir, bu da istenen davranış değildir.
D3l_Gato

-4

kuyruk -r çoğu Linux ve MacOS sisteminde çalışır

sıra 1 20 | kuyruk -r


-9
sort -r < filename

veya

rev < filename

7
sort -ryalnızca giriş zaten sıralanmışsa çalışır, bu durum burada böyle değildir. revsatır başına karakterleri tersine çevirir, ancak satır sırasını olduğu gibi tutar. Yani bu cevap aslında hiç cevap değil.
Alexander Stumpf
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.