Řekněme, že mám obrovský textový soubor (> 2 GB) a chci pouze cat
řádky X
až Y
(např. 57890000 až 57890010).
Z toho, co jsem pochopil, mohu to udělat tím, že pošlu head
do tail
nebo viceversa, tj.
head -A /path/to/file | tail -B
nebo alternativně
tail -C /path/to/file | head -D
kde A
, B
, C
a D
lze vypočítat z počtu řádků v souboru, X
a Y
.
S tímto přístupem však existují dva problémy:
A
, B
, C
a D
.pipe
mnohem více řádků, než mám zájem o čtení (např. Pokud přečtu jen několik řádků uprostřed obrovského souboru)Existuje způsob, jak Shell prostě pracovat a vydávat řádky, které chci? (zatímco poskytujete pouze X
a Y
)?
Navrhuji řešení sed
, ale kvůli úplnosti
awk 'NR >= 57890000 && NR <= 57890010' /path/to/file
Vystřihnout za poslední řádek:
awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file
Test rychlosti (zde na MacOS, YMMV na dalších systémech):
seq 100000000 > test.in
real
čas podle vestavěného bash
time
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
V žádném případě to nejsou přesná měřítka, ale rozdíl je dostatečně jasný a opakovatelný *, aby poskytl dobrý pocit relativní rychlosti každého z těchto příkazů.
*: Kromě prvních dvou, sed -n p;q
a head|tail
, které se zdají být v podstatě stejné.
Pokud chcete, aby řádky X až Y včetně (začátek číslování na 1), použijte
tail -n "+$X" /path/to/file | head -n "$((Y-X+1))"
tail
přečte a zahodí první řádky X-1 (neexistuje žádný způsob), pak přečte a vytiskne následující řádky. head
přečte a vytiskne požadovaný počet řádků a poté skončí. Když head
skončí, tail
obdrží signál SIGPIPE a zemře, takže nebude číst více než vyrovnávací paměť velikost má hodnotu (obvykle několik kilobajtů) řádků ze vstupního souboru.
Alternativně, jak gorkypl navrhl, použijte sed:
sed -n -e "$X,$Y p" -e "$Y q" /path/to/file
Řešení sed je však výrazně pomalejší (alespoň pro GNU utilitky a utilitky Busybox; sed může být konkurenceschopnější, pokud rozbalíte velkou část souboru na OS, kde je potrubí pomalé a sed je Zde jsou rychlé testy v systému Linux, data byla vygenerována programem seq 100000000 >/tmp/a
, prostředím je Linux/AMD64, /tmp
je tmpfs a stroj je jinak nečinný a nevyměňuje se.
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
Pokud znáte rozsah bajtů, se kterým chcete pracovat, můžete jej rychleji extrahovat přeskočením přímo na počáteční pozici. Ale pro řádky musíte číst od začátku a počítat nové řádky. Pro extrakci bloků z x inclusive na y exkluzivní počínaje 0, s velikostí bloku b:
dd bs="$b" seek="$x" count="$((y-x))" </path/to/file
The head | tail
přístup je jedním z nejlepších a nejvíce "idiomatických" způsobů, jak toho dosáhnout:
X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"
Jak poznamenal Gilles v komentářích, rychlejší cesta je
< infile.txt tail -n +"$X" | head -n "$((Y - X))"
Důvodem je to rychlejší je, že první X - 1 řádky nemusí procházet potrubím ve srovnání s head | tail
přístup.
Vaše dotazovaná formulace je trochu zavádějící a pravděpodobně vysvětluje některá z vašich nepodložených pochybností k tomuto přístupu.
Říkáte, že musíte vypočítat A
, B
, C
, D
, ale jak vidíte, počet řádků souboru není potřeba a na je nutný nejvýše 1 výpočet, který pro vás Shell stejně může udělat.
Bojíte se, že potrubí přečte více řádků, než je nutné. Ve skutečnosti to není pravda: tail | head
je asi tak efektivní, jak můžete získat, pokud jde o soubor I/O. Nejprve zvažte minimální potřebnou práci: najít X 'řádek v souboru, jediný obecný způsob, jak to udělat, je přečíst každý bajt a zastavte se, když počítáte X symboly nových řádků, protože neexistuje žádný způsob, jak zabránit posunutí souboru X 'th line. Jakmile dosáhnete * X * th řádku, musíte si přečíst všechny řádky, abyste je mohli vytisknout, zastavení na Y 'th řádku. Žádný přístup se tedy nemůže dostat pryč při čtení méně než Y řádků. Nyní, head -n $Y
nečte více než Y řádků (zaokrouhleno na nejbližší vyrovnávací jednotku, ale vyrovnávací paměti, pokud jsou použity správně, zlepšují výkon, takže se nemusíte starat o tuto režii) . Navíc tail
nebude číst více než head
, takže jsme ukázali, že head | tail
přečte nejmenší možný počet řádků (opět plus nějaké zanedbatelné ukládání do vyrovnávací paměti, které ignorujeme). Jediná výhoda efektivnosti jediného přístupu k nástroji, který nepoužívá potrubí, je méně procesů (a tedy méně režijních nákladů).
Nej ortodoxnějším způsobem (ale ne nejrychlejším, jak poznamenal Gilles výše) by bylo použití sed
.
Ve vašem případě:
X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename
The -n
volba znamená, že se do výtisku vytisknou pouze příslušné řádky.
p na konci čísla cílové čáry znamená tisk linek v daném rozsahu. q ve druhé části skriptu ušetří nějaký čas přeskočením zbytku souboru.
Pokud známe rozsah, který chceme vybrat, od prvního řádku: lStart
do posledního řádku: lEnd
bychom mohli spočítat:
lCount="$((lEnd-lStart+1))"
Známe-li celkový počet řádků: lAll
, můžeme také vypočítat vzdálenost do konce souboru:
toEnd="$((lAll-lStart+1))"
Pak budeme vědět oba:
"how far from the start" ($lStart) and
"how far from the end of the file" ($toEnd).
Výběr nejmenší z těchto: tailnumber
takto:
tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"
Umožňuje nám používat důsledně nejrychlejší prováděcí příkaz:
tail -n"${tailnumber}" ${thefile} | head -n${lCount}
Vezměte prosím na vědomí další znaménko plus („+“), když $linestart
je vybráno.
Jedinou výzvou je, že potřebujeme celkový počet řádků, a to může nějakou dobu trvat, než najdeme.
Jak je obvyklé u:
linesall="$(wc -l < "$thefile" )"
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
Upozorňujeme, že časy se drasticky mění, pokud jsou vybrané řádky blízko začátku nebo konce. Příkaz, který vypadá dobře na jedné straně souboru, může být na druhé straně souboru velmi pomalý.
Dělám to dost často, a tak napsal tento skript. Nepotřebuji najít čísla řádků, skript to všechno provede.
#!/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