cat line X büyük bir dosyada Y'ye


132

Diyelim ki çok büyük bir metin dosyasına sahibim (> 2GB) ve sadece catsatırlara Xgitmek istiyorum Y(örneğin 57890000 - 57890010).

Anladığım kadarıyla ben boru yapabilirsiniz headiçine tailyani ya viceversa

head -A /path/to/file | tail -B

Veya alternatif olarak

tail -C /path/to/file | head -D

burada A, B, Cve Ddosya satır sayısı hesaplanabilir, Xve Y.

Ancak bu yaklaşımla ilgili iki sorun var:

  1. Sen hesaplamak zorunda A, B, Cve D.
  2. Komutları olabilir pipebirbirlerine çok daha fazla (Ben büyük dosyasının ortasında sadece birkaç satır okuyorum eğer örneğin) Okumayı ilgileniyorum daha hatlar

Kabuğun sadece çalışıp istediğim satırları çıkarmasının bir yolu var mı? (sadece temin ederken Xve Y)?


1
Bilginize, gerçek hız testi karşılaştırmasına cevabımdaki 6 yöntem eklendi.
Kevin,

Yanıtlar:


119

sedÇözümü öneriyorum , ancak eksiksiz olması adına,

awk 'NR >= 57890000 && NR <= 57890010' /path/to/file

Son satırdan sonra kesmek için:

awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file

Hız testi:

  • Tarafından oluşturulan 100.000.000 satırlık dosya seq 100000000 > test.in
  • 50.000.000-50.000.010 satırlarını okuma
  • Belirli bir düzende olmayan testler
  • realbashyerleşik olarak bildirilen süretime
 4.373  4.418  4.395    tail -n+50000000 test.in | head -n10
 5.210  5.179  6.181    sed -n '50000000,50000010p;57890010q' test.in
 5.525  5.475  5.488    head -n50000010 test.in | tail -n10
 8.497  8.352  8.438    sed -n '50000000,50000010p' test.in
22.826 23.154 23.195    tail -n50000001 test.in | head -n10
25.694 25.908 27.638    ed -s test.in <<<"50000000,50000010p"
31.348 28.140 30.574    awk 'NR<57890000{next}1;NR==57890010{exit}' test.in
51.359 50.919 51.127    awk 'NR >= 57890000 && NR <= 57890010' test.in

Bunlar hiçbir şekilde kesin ölçüt değildir, ancak bu komutların her birinin göreceli hızının iyi bir şekilde anlaşılması için yeterince açık ve tekrarlanabilir *.

*: İlk iki arasında dışında sed -n p;qve head|tailtemelde aynı olduğu görülüyor.


11
Meraktan: Testler arasındaki disk önbelleğini nasıl temizlediniz?
Paweł Rumian

2
Peki tail -n +50000000 test.in | head -n10, hangisi aksine tail -n-50000000 test.in | head -n10, doğru sonucu verir?
Gilles

4
Tamam, gidip bazı ölçütler yaptım. tail | head sedden çok daha hızlı, fark beklediğimden çok daha fazla.
Gilles,

3
@ Gilles haklısın benim hatam. tail+|headsed 'e göre% 10-15 daha hızlı, bu kriteri ekledim.
Kevin,

1
Sorunun satır sorar, ancak -ckarakterleri atlamak için kullanırsanız tail+|headani olduğunu fark ediyorum . Tabii ki, "50000000" diyemezsiniz ve aradığınız bölümün başlangıcını elle aramak zorunda kalabilirsiniz.
Danny Kirchmeier

51

X - Y satırlarını dahil etmek istiyorsanız (numaralandırma 1'den başlar),

tail -n +$X /path/to/file | head -n $((Y-X+1))

taililk X-1 satırlarını okuyup atar (bunun yolu yoktur), sonra aşağıdaki satırları okuyup yazdırır. headİstenilen satır sayısını okuyup yazdırır, sonra çıkar. Ne zaman headçıkışlar, tailbir alır SIGPIPE sinyali ve ölür, bu yüzden giriş dosyasından satır arabellek boyutu yetmeyecek (genellikle birkaç kilobayt) daha okudum olmayacaktır.

Alternatif olarak, gorkypl'in önerdiği gibi sed kullanın:

sed -n -e "$X,$Y p" -e "$Y q" /path/to/file

Sed çözümü önemli ölçüde daha yavaş olmasına rağmen (en azından GNU yardımcı programları ve Busybox yardımcı programları için; dosyanın yavaş ve sed'nin hızlı olduğu bir işletim sistemine dosyanın büyük bir bölümünü çıkarırsanız daha rekabetçi olabilir). İşte Linux altında hızlı kriterler; veri üretildi seq 100000000 >/tmp/a, ortam Linux / amd64, /tmptmpfs ve makine aksi durumda boşta ve değişmiyor.

real  user  sys    command
 0.47  0.32  0.12  </tmp/a tail -n +50000001 | head -n 10 #GNU
 0.86  0.64  0.21  </tmp/a tail -n +50000001 | head -n 10 #BusyBox
 3.57  3.41  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #GNU
11.91 11.68  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #BusyBox
 1.04  0.60  0.46  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #GNU
 7.12  6.58  0.55  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #BusyBox
 9.95  9.54  0.28  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #GNU
23.76 23.13  0.31  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #BusyBox

Çalışmak istediğiniz bayt aralığını biliyorsanız, doğrudan başlangıç ​​konumuna geçerek daha hızlı çıkarabilirsiniz. Ancak satırlar için baştan okumak ve yeni satırları saymak zorundasınız. B dahil blok büyüklüğünde, 0'dan başlayarak x'ten y'ye özel blokları çıkarmak için:

dd bs=$b seek=$x count=$((y-x)) </path/to/file

1
Aralarında önbellek olmadığından emin misin? Tail | head ve sed arasındaki farklar benim için çok büyük görünüyor.
Paweł Rumian

@gorkypl Birkaç önlem yaptım ve zaman karşılaştırılabilir. Yazdığım gibi, bunların hepsi RAM'de oluyor (her şey önbellekte).
Gilles

1
@ Gilles tail will read and discard the first X-1 linekaçınılmaz, hat sayısı sondan verildiğinde, bu durumda, kuyruk çalışma zamanına göre uçtan geriye doğru okunuyor gibi görünmektedir. Lütfen okuyun: http://unix.stackexchange.com/a/216614/79743.

1
@BinaryZebra Evet, giriş normal bir dosya ise, bazı uygulamaların tail(GNU kuyruğu dahil) en baştan okumak için sezgisel özellikleri vardır. Bu, tail | headdiğer yöntemlere kıyasla çözümü geliştirir .
Gilles

22

head | tailYaklaşım Bunu yapmanın en iyi ve en "deyimsel" yollarından biridir:

X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"

Gilles tarafından yorumlarda belirtildiği gibi, daha hızlı bir yol

< infile.txt tail -n +"$X" | head -n "$((Y - X))"

Bunun daha hızlı olmasının sebebi, ilk X - 1 hattının head | tailyaklaşıma kıyasla borudan geçmesine gerek olmamasıdır .

İfadeli olarak sorunuz biraz yanıltıcıdır ve muhtemelen bu yaklaşıma yönelik asılsız yanlışlıklarınızı açıklar.

  • Sen hesaplamak gerektiğini söylüyorlar A, B, C, Dgördüğünüz gibi ancak, dosyanın satır sayısı gerekli değildir ve en fazla 1 hesaplama kabuk neyse sizin için yapabileceği hangi gereklidir.

  • Borunun gereğinden fazla satır okuyacağından endişeleniyorsunuz. Aslında bu doğru değil: tail | headG / Ç dosyası bakımından alabileceğiniz kadar verimli. Öncelikle, gerekli asgari iş miktarını göz önünde bulundurun: X'in çizgisini bir dosyada bulmak için, onu yapmanın tek genel yolu, her baytı okumak ve dosyayı açmak için X newline sembolünü saydığınızda durmaktır. X çizgisinin uzaklığı . * X * inci satıra ulaştıktan sonra, yazdırmak için tüm satırları okumak ve Y satırında durmak zorundasınız . Böylece hiçbir satır Y çizgisinden daha az okumaktan kaçamaz. Şimdi, Y’denhead -n $Y fazla değilçizgiler (en yakın tampon ünitesine yuvarlanır, ancak tamponlar doğru kullanılırsa performansı arttırır, bu nedenle bu ek yük için endişelenmenize gerek yoktur). Ek olarak, tailbundan daha fazlasını okumaz head, bu yüzden head | tailmümkün olan en az satır sayısını okuduğumuzu gösterdik (yine, görmezden geldiğimiz bazı önemsiz tamponlamalar). Boru kullanmayan tek bir takım yaklaşımının tek verimlilik avantajı daha az işlemdir (ve dolayısıyla daha az ek yük).


1
Yönlendirmeyi daha önce satırda ilk kez izlememiştim. Soğuk, boru akışını daha net hale getirir.
clacke

14

En ortodoks yol (ancak yukarıda belirtilen Gilles tarafından belirtildiği gibi en hızlı değil ) kullanmaktır sed.

Senin durumunda:

X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename

Bu -nseçenek, yalnızca ilgili satırların stdout'a yazdırıldığı anlamına gelir.

Son satır numarasının sonundaki p , verilen aralıktaki satırları yazdırmak anlamına gelir. Q, komut ikinci bölümünde dosyanın geri kalan atlayarak bir zaman tasarrufu sağlar.


1
Beklediğim gibi sedve tail | headeşit olmak üzereydi, ancak bunun tail | headdaha hızlı olduğu ortaya çıktı ( cevabımı görün ).
Gilles

1
Bilmiyorum, okuduklarımdan tail/ headdaha "ortodoks" olarak kabul edilirler, çünkü bir dosyanın her iki ucunun kesilmesi tam olarak onlar için yapılır. Bu malzemelerde, sedsadece ikame gerektiğinde resme giriyor gibi görünüyor - ve karmaşık işler için sözdizimi AWK'dan çok daha kötü olduğundan, daha karmaşık bir şey olmaya başladığında resimden hızla çekiliyor .
underscore_d

7

Seçilecek aralığı biliyorsak, ilk satırdan: lStartson satıra kadar: lEndhesaplayabiliriz:

lCount="$((lEnd-lStart+1))"

Toplam satır sayısını biliyorsak: lAlldosyanın sonuna kadar olan mesafeyi de hesaplayabiliriz:

toEnd="$((lAll-lStart+1))"

O zaman ikisini de bileceğiz:

"how far from the start"            ($lStart) and
"how far from the end of the file"  ($toEnd).

Bunlardan en küçüğünü seçmek: tailnumberBu:

tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"

Sürekli olarak en hızlı çalıştırma komutunu kullanmamızı sağlar:

tail -n"${tailnumber}" ${thefile} | head -n${lCount}

Lütfen $linestartseçildiğinde ek artı ("+") işaretine dikkat edin .

Tek uyarı, toplam satır sayısına ihtiyacımız olduğu ve bunun bulunması biraz zaman alabilir.
Her zamanki gibi:

linesall="$(wc -l < "$thefile" )"

Ölçülen bazı zamanlar:

lStart |500| lEnd |500| lCount |11|
real   user   sys    frac
0.002  0.000  0.000  0.00  | command == tail -n"+500" test.in | head -n1
0.002  0.000  0.000  0.00  | command == tail -n+500 test.in | head -n1
3.230  2.520  0.700  99.68 | command == tail -n99999501 test.in | head -n1
0.001  0.000  0.000  0.00  | command == head -n500 test.in | tail -n1
0.001  0.000  0.000  0.00  | command == sed -n -e "500,500p;500q" test.in
0.002  0.000  0.000  0.00  | command == awk 'NR<'500'{next}1;NR=='500'{exit}' test.in


lStart |50000000| lEnd |50000010| lCount |11|
real   user   sys    frac
0.977  0.644  0.328  99.50 | command == tail -n"+50000000" test.in | head -n11
1.069  0.756  0.308  99.58 | command == tail -n+50000000 test.in | head -n11
1.823  1.512  0.308  99.85 | command == tail -n50000001 test.in | head -n11
1.950  2.396  1.284  188.77| command == head -n50000010 test.in | tail -n11
5.477  5.116  0.348  99.76 | command == sed -n -e "50000000,50000010p;50000010q" test.in
10.124  9.669  0.448  99.92| command == awk 'NR<'50000000'{next}1;NR=='50000010'{exit}' test.in


lStart |99999000| lEnd |99999010| lCount |11|
real   user   sys    frac
0.001  0.000  0.000  0.00  | command == tail -n"1001" test.in | head -n11
1.960  1.292  0.660  99.61 | command == tail -n+99999000 test.in | head -n11
0.001  0.000  0.000  0.00  | command == tail -n1001 test.in | head -n11
4.043  4.704  2.704  183.25| command == head -n99999010 test.in | tail -n11
10.346  9.641  0.692  99.88| command == sed -n -e "99999000,99999010p;99999010q" test.in
21.653  20.873  0.744  99.83 | command == awk 'NR<'99999000'{next}1;NR=='99999010'{exit}' test.in

Seçilen satırlar başlangıcı veya bitişi yakınındaysa, zamanın büyük ölçüde değiştiğini unutmayın. Dosyanın bir tarafında iyi çalışıyor gibi görünen bir komut, dosyanın diğer tarafında çok yavaş olabilir.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
terdon

@BinaryZebra - yol daha iyi.
mikeserv

0

Bunu yeterince sık yapıyorum ve bu yüzden bu senaryoyu yazdım. Satır numaralarını bulmama gerek yok, senaryo hepsini yapıyor.

#!/bin/bash

# $1: start time
# $2: end time
# $3: log file to read
# $4: output file

# i.e. log_slice.sh 18:33 19:40 /var/log/my.log /var/log/myslice.log

if [[ $# != 4 ]] ; then 
echo 'usage: log_slice.sh <start time> <end time> <log file> <output file>'
echo
exit;
fi

if [ ! -f $3 ] ; then
echo "'$3' doesn't seem to exit."
echo 'exiting.'
exit;
fi

sline=$(grep -n " ${1}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of start time
eline=$(grep -n " ${2}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of end time

linediff="$((eline-sline))"

tail -n+${sline} $3|head -n$linediff > $4

2
Sorulmamış bir soruyu cevaplıyorsun. Cevabınız, tail|headsoruda ve diğer cevaplarda yoğun şekilde tartışılan % 10'dur ve% 90'ı, sorgunun bir parçası olmayan , belirtilen dizgilerin / kalıpların göründüğü satır numaralarını belirler . PS her zaman kabuk parametrelerinizi ve değişkenlerinizi belirtmelisiniz; örneğin, "3 $" ve "4 $".
G-Man
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.