строка кошки X к строке Y на огромном файле

Установите переменную среды CVSEDITOR Вашему любимому редактору. Это не расширено оболочкой, таким образом, тильда не будет работать. Так запишите короткую обертку оболочки и призовите это CVSEDITOR. Запишите это как ~/bin/CVSEDITOR и сделайте это исполняемым файлом:

#!/bin/sh
exec vim -S ~/myscript.vim

Затем вставьте следующую строку Ваш ~/.profile:

export CVSEDITOR=CVSEDITOR

Если Вы хотите использовать того же редактора для всего, установите EDITOR и VISUAL переменные вместо этого (устанавливает оба на то же значение).

136
05.03.2013, 15:13
7 ответов

Я предлагаю sed решение, но ради полноты,

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

Отключать после последней строки:

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

Тест скорости (здесь на macOS, YMMV в других системах):

  • Файл с 100,000,000 строками, сгенерированный seq 100000000 > test.in
  • Чтение строк 50 000 000-50 000 010
  • Тесты без определенного порядка
  • real время, как сообщается 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

Это ни в коем случае не точные сравнительные тесты, но различие является четким и достаточно повторяемое* для предоставления хорошего чувства относительной скорости каждой из этих команд.

*: Кроме между первыми двумя, sed -n p;q и head|tail, которые, кажется, по существу то же.

124
27.01.2020, 19:29
  • 1
    Из любопытства: как Вы сбросили дисковый кэш между тестами? –  Paweł Rumian 08.09.2012, 11:08
  • 2
    Что относительно tail -n +50000000 test.in | head -n10, который, в отличие от этого, tail -n-50000000 test.in | head -n10 дал бы корректный результат? –  Gilles 'SO- stop being evil' 08.09.2012, 13:55
  • 3
    Хорошо, я пошел и сделал некоторые сравнительные тесты. tail|head является путем быстрее, чем sed, различие намного больше, чем я ожидал. –  Gilles 'SO- stop being evil' 08.09.2012, 14:30
  • 4
    @Gilles Вы правы, мое плохое. tail+|head быстрее 10-15%, чем sed, я добавил тот сравнительный тест. –  Kevin 08.09.2012, 16:50
  • 5
    я понимаю, что вопрос просит строки, но если Вы используете -c пропускать символы, tail+|head мгновенно. Конечно, Вы не можете сказать "50000000" и, вероятно, придется вручную найти запуск раздела, который Вы ищете. –  Danny Kirchmeier 25.04.2014, 19:26

Если Вы хотите строки X к Y включительно (начинающий нумерацию в 1), использовать

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

tail считает и отбросит первые X-1 строки (нет никакого пути вокруг того), затем считайте и распечатайте следующие строки. head считает и распечатает требуемое количество строк, затем выйдет. Когда head выходы, tail получает сигнал SIGPIPE и умирает, таким образом, он не будет читать больше, чем ценность размера буфера (обычно несколько килобайтов) строк из входного файла.

С другой стороны, как gorkypl предложенный, используйте sed:

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

sed решение значительно медленнее хотя (по крайней мере, для утилит GNU и утилит Busybox; sed мог бы быть более конкурентоспособным при извлечении значительной части файла на ОС, где передача по каналу является медленной, и sed быстр). Вот быстрые сравнительные тесты в соответствии с Linux; данные были сгенерированы seq 100000000 >/tmp/a, среда является Linux/amd64, /tmp tmpfs, и машина в других отношениях неактивна и не подкачивает.

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

Если Вы знаете диапазон байта, Вы хотите работать с, можно извлечь его быстрее путем пропуска непосредственно к позиции запуска. Но для строк, необходимо читать с начала и считать новые строки. Извлечь блоки из x включительно к y эксклюзивному запуску в 0, с размером блока b:

dd bs="$b" seek="$x" count="$((y-x))" </path/to/file
54
27.01.2020, 19:29
  • 1
    Вы уверены, что нет никакого промежутка кэширования? Различия между tail|head и sed кажутся слишком большими мне. –  Paweł Rumian 08.09.2012, 15:03
  • 2
    @gorkypl я сделал несколько мер и времена, был сопоставим. Как я записал, это все происходит в RAM (все находится в кэше). –  Gilles 'SO- stop being evil' 08.09.2012, 15:06
  • 3
    @Gilles tail will read and discard the first X-1 line кажется, избегают, когда количество строк дано от конца В таком случае, хвост, кажется, читает назад от конца согласно выполняющимся временам. Читайте: http://unix.stackexchange.com/a/216614/79743. –   17.07.2015, 07:52
  • 4
    @BinaryZebra пожатия плеч Да, если вход является регулярным файлом, некоторыми реализациями tail (включая хвост GNU), имеют эвристику для чтения из конца. Это улучшается tail | head решение по сравнению с другими методами. –  Gilles 'SO- stop being evil' 17.07.2015, 10:08

Самый православный путь (но не самое быстрое, как отмечено Gilles выше) состоял бы в том, чтобы использовать sed.

В Вашем случае:

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

-n опция подразумевает, что только соответствующие строки печатаются к stdout.

P в конце числа финишной черты означает печатать строки в данном диапазоне. Q во второй части сценария экономит некоторое время путем пропуска остатка от файла.

16
27.01.2020, 19:29
  • 1
    я ожидал sed и tail | head чтобы быть о наравне, но это оказывается этим tail | head значительно быстрее (см. мой ответ). –  Gilles 'SO- stop being evil' 08.09.2012, 14:31
  • 2
    , который я не знаю, от того, что я считал, tail/head считаются более "православными", начиная с обрезки любого конца файла точно, для чего они сделаны. В тех материалах, sed только, кажется, вводит изображение, когда замены требуются - и быстро быть продвинутыми из изображения, когда что-либо, намного более сложные запуски для случая так как его синтаксис для сложных задач настолько хуже, чем AWK, который затем вступает во владение. –  underscore_d 08.10.2016, 14:40

head | tail подход является одним из лучших и большинства "идиоматических" способов сделать это:

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

Как указано Gilles в комментариях, более быстрый путь

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

Причиной это быстрее, является первое X - 1 строка не должна проходить канал по сравнению с head | tail подход.

Ваш вопрос, как формулируется является немного вводящим в заблуждение и вероятно объясняет некоторые Ваши необоснованные опасения к этому подходу.

  • Вы говорите, что необходимо вычислить A, B, C, D но как Вы видите, количество строки файла не нужно, и самое большее 1 вычисление необходимо, который оболочка может сделать для Вас так или иначе.

  • Вы волнуетесь, что передача по каналу считает больше строк, чем необходимый. На самом деле это не верно: tail | head почти так эффективно, как можно добраться с точки зрения файлового ввода-вывода. Во-первых, считайте минимальный объем работы необходимым: для нахождения строки X'th в файле единственный общий способ сделать это состоит в том, чтобы считать каждый байт и остановку когда Вы символы новой строки количества X, поскольку нет никакого способа предугадать файловое смещение строки X'th. После того как Вы достигаете *строка X*th, необходимо считать все строки для печати их, остановившись в строке Y'th. Таким образом никакому подходу не может сойти с рук чтение меньше, чем строки Y. Теперь, head -n $Y чтения не больше, чем Y строки (округленный к ближайшей буферной единице, но буферам, если используется правильно улучшают производительность, таким образом, никакая потребность волноваться о тех издержках). Кроме того, tail не будет больше читать, чем head, таким образом, таким образом мы показали это head | tail наименьшее количество чтений нумерует возможных строк (снова, плюс некоторая незначительная буферизация, которую мы игнорируем). Единственным преимуществом эффективности единственного подхода инструмента, который не использует каналы, является меньше процессов (и таким образом меньше служебное).

23
27.01.2020, 19:29
  • 1
    Никогда не замечаемый перенаправление пойти сначала на строке прежде. Прохладный, это заставляет канал течь более ясный. –  clacke 27.04.2016, 06:42

Я делаю этого достаточно часто и так написал этот скрипт. Мне не нужно найти номера строк, скрипт делает все это.

#!/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
1
27.01.2020, 19:29

Если мы знаем диапазон для выбора, от первой строки: lStart до последней строки: lEnd , мы можем вычислить:

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

Если мы знаем общее количество строк : lAll , мы также можем вычислить расстояние до конца файла:

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

Тогда мы узнаем и то, и другое:

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

Выбираем наименьшее из них: tailnumber как это :

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

Позволяет нам использовать наиболее быстро выполняющуюся команду:

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

Обратите внимание на дополнительный знак плюса («+»), когда выбран $ linestart .

Единственное предостережение: нам нужно общее количество строк, и это может занять некоторое время, чтобы найти.
Как обычно с:

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

Обратите внимание, что время резко меняется, если выбранные строки находятся около начала или ближе к концу. Команда, которая хорошо работает на одной стороне файла, может быть очень медленной на другой стороне файла.

7
27.01.2020, 19:29

Если вы cat данные, вы захотите использовать сначала хвост, а затем голову.

cat file.name | tail -n +"3" | head -n -"1"
-1
27.07.2020, 23:44

Теги

Похожие вопросы