Инструмент Bash для получения n-ой строки из файла

Есть ли "канонический" способ сделать это? Я использую head -n | tail -1, что делает трюк, но мне стало интересно, есть ли в Bash инструмент, который специально извлекает строку (или диапазон строк) из файла.

Под "каноническим" я имею в виду программу, основная функция которой заключается в этом.

Комментарии к вопросу (5)
Решение

head и pipe с tail будет медленным для огромного файла. Я бы предложил использовать ed следующим образом:

sed 'NUMq;d' file

Где NUM - номер строки, которую вы хотите вывести; так, например, sed '10q;d' file выведет 10-ю строку file.

Объяснение:

NUMq немедленно завершит работу, когда номер строки будет равен NUM.

d удалит строку вместо ее печати; это запрещено на последней строке, потому что q заставляет пропустить остальную часть сценария при выходе.

Если у вас есть NUM в переменной, вы захотите использовать двойные кавычки вместо одинарных:

sed "${NUM}q;d" file
Комментарии (26)
sed -n '2p' < file.txt

выведет вторую строку

sed -n '2011p' < file.txt

2011-я строка

sed -n '10,33p' < file.txt

строка 10 до строки 33

sed -n '1p;3p' < file.txt

1-я и 3-я строки

и так далее...

Для добавления строк с помощью sed, вы можете проверить это:

https://stackoverflow.com/q/15555781/#16246806

Комментарии (8)

У меня уникальная ситуация, когда можно тест решения, предложенные на данной странице, и поэтому я'м пишу этот ответ как консолидация предлагаемого решения с включенными раз для каждого.

Настройка

У меня 3.261 гигабайт текстовых данных файл с одну пару ключ-значение для каждой строки. Файл содержит 3,339,550,320 строк в целом и не поддается открытию в любом редакторе я пробовал, в том числе и мой ВИМ. Мне нужно, чтобы подмножество этого файла, чтобы исследовать некоторые из тех ценностей, которые я've обнаружил только начальную строку ~500,000,000.

Поскольку файл имеет очень много строк:

  • Мне нужно извлечь только подмножество строк, чтобы сделать что-то полезное с данными.
  • Читая каждую строку, ведущих к значения, что меня волнует, это собирается занять много времени.
  • Если решение читает мимо строк, что меня волнует и продолжает читать остальные файла он будет тратить время на чтение почти 3 миллиарда несущественных строк и принимать 6 раз дольше, чем необходимо.

Мои сценарии-это решение, которое извлекает только одну строку из файла без чтения любой другой строке в файле, но я могу'т думаю, что я бы сделать это в bash.

Для целей моей вменяемости я'м не собираюсь быть пытаясь прочитать полный 500,000,000 линий Я'd нужен для моей проблемы. Вместо этого я'll быть пытаясь извлечь строку 50,000,000 из 3,339,550,320 (что означает, прочитав полный файл займет 60х дольше, чем это необходимо).

Я буду использовать "время" в ориентир каждой команды.

Базовый

Сначала позвольте's смотреть, как "голова ""хвост" решение:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

Исходные данные для строки 50 млн. 00:01:15.321, если я'd и пошли прямо по строке 500 млн. Это'd и, наверное, ~12,5 минут.

вырезать

Я'м сомнительный, но это'ы стоит попробовать:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

Этот взял 00:05:12.156 для запуска, который намного медленнее, чем базовый! Я'м не уверен, сможет ли он прочитать весь файл или только до линии 50 млн. до остановки, но независимо от этого не'т, кажется, как жизнеспособное решение проблемы.

Неужели

Мне показалось, что это решение с "выхода", потому что я вовсе'т собираюсь ждать полный файл для запуска:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

Этот код выполнялся в 00:01:16.583, который находится всего в ~1 секунду медленнее, но все равно не лучше базового. Такими темпами если команда exit были исключены, он, вероятно, принял около ~76 минут, чтобы прочитать весь файл!

Перль

Я проверил существующие решения на Perl, а также:

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

Этот код выполнялся в 00:01:13.146, что в ~2 секунды быстрее, чем базовый. Если я'd и запустить его на полную 500,000,000 это, вероятно, займет ~12 минут.

ООО

Верхний ответ на доске, здесь's мой результат:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

Этот код выполнялся в 00:01:12.705, что на 3 секунды быстрее, чем базовая, и ~0,4 секунды быстрее, чем Perl. Если я'd и запустить его на полную 500,000,000 строки, он, вероятно, принял ~12 минут.

карты

У меня есть bash 3.1 и поэтому не могу проверить на примере решения.

Вывод

Похоже, по большей части, это'ы трудно улучшить решение "голова ""хвост". В лучшем случае решение СЭД дает ~3% Увеличение эффективности.

(в процентах вычисляется по формуле % = (выполнения/базовый уровень - 1) * 100)

Строки 50,000,000

  1. 00:01:12.705 (-00:00:02.616 = -3.47%) ООО
  2. 00:01:(-00 13.146 :00:02.175 = -2.89%) на Perl
  3. 00:01:15.321 (+00:00:глава 00.000 = +0.00%) |хвост
  4. 00:01:16.583 (+00:00:01.262 = +1.68%) на awk
  5. 00:05:12.156 (+00:03:56.835 = +314.43%) `вырезать

Строки 500,000,000

  1. 00:12:07.050 (-00:00:26.160) ООО
  2. 00:12:11.460 (-00:00:21.750) на Perl
  3. 00:12:33.210 (+00:00:00.000) голова|хвост
  4. 00:12:45.830 (+00:00:12.620) на awk
  5. 00:52:01.560 (+00:40:31.650) `вырезать

Строки 3,338,559,320

  1. 01:20:54.599 (-00:03:05.327) ООО
  2. 01:21:24.045 (-00:02:25.227) на Perl
  3. 01:23:49.273 (+00:00:00.000) голова|хвост
  4. 01:25:13.548 (+00:02:35.735) на awk
  5. 05:47:23.026 (+04:24:26.246) `вырезать
Комментарии (1)

С в awk это довольно быстро:

awk 'NR == num_line' file

Если это правда, то поведение значения по умолчанию в awk выполняется: {печати $0}.


Альтернативные версии

Если ваш файл, случается огромное, вы'выход D лучше после прочтения нужной линии. Таким образом вы экономите процессорное время.

awk 'NR == num_line {print; exit}' file

Если вы хотите дать номер строки из Баш переменной можно использовать:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent
Комментарии (3)

Вау, все возможности!

Попробуйте это:

sed -n "${lineNum}p" $file

или один из них в зависимости от вашей версия awk:

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

(Содержаться материалы, возможно, придется попробовать command_ в nawk или поглазеть).

Есть ли инструмент, который не только распечатать, что конкретной линии? Не один из стандартных инструментов. Однако, СЭД - это, наверное, самый близкий и простой в использовании.

Комментарии (0)

По моим расчетам, с точки зрения производительности и читабельности моя рекомендация:

хвоста -Н+Н | глава -1`

N-это номер строки, которую вы хотите. Например, хвоста -н+7 input.txt | руководитель -1` будет печатать 7-й строке файла.

хвоста -н+нбудет печатать все, начиная от строкиП, иHead -1` заставит его остановиться после одной линии.


Руководитель -Н альтернатива | хвост -1, пожалуй, немного более читабельным. Например, это будет печать 7-й строке:

глава -7 input.txt | хвост -1`

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

Топ-проголосовали-Current функциональности sed &#39;NUMq;д&#39; интересно знать, но я утверждаю, что это будет воспринято меньше людей из коробки, чем голова/хвост решение, и это также медленнее, чем хвост/голову.

В моих тестах, обе хвосты/головы превзошли версии-Current функциональности sed &#39;NUMq;д&#39; последовательно. В соответствии с другими критериями, которые были размещены. Трудно найти случай, когда хвосты/головы было очень плохо. Это тоже не удивительно, так как эти операции, которые вы ожидаете, чтобы быть сильно оптимизирован в современной системе Unix.

Чтобы получить представление о различиях в производительности, это число, которое я получаю огромный файл (9,3 г):

  • хвост -П+Н | голову -1`: 3.7 сек
  • глава -Н | хвост -1: 4.6 сек
  • СЭД ПД;д: 18.8 сек

Результаты могут отличаться, но голова результативность | хвост " и " хвост | голова, в общем-то, сопоставимы для маленьких входов, и СЭД всегда медленнее значительным фактором (около 5X или около того).

Чтобы воспроизвести мой тест, вы можете попробовать следующее, но предупредил, что это создаст файл 9.3 G в текущей рабочей директории:

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

Вот результат работы на моей машине (ThinkPad в Х1 карбон с SSD и 16 ГБ памяти). Я предполагаю, что в конечном итоге все выйдет из кэша, а не с диска:

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s
Комментарии (4)

Этот вопрос меткам Баш, здесь'ы Баш (≥4) способ совершения: применение карты с (Skip) к (посчитайте) вариант.

Если вам нужно получить 42-й строке файла файл``:

mapfile -s 41 -n 1 ary < file

На данный момент, Вы'будете иметь массив Ары области, которая содержит строки "файл" (включая пустую строку), где мы пропустили первый 41 линии (-с 41), и остановился после прочтения одной строки (-Н 1). Так что's действительно 42-й строке. Чтобы распечатать его:

printf '%s' "${ary[0]}"

Если вам нужен ассортимент, говорят 42-666 диапазоне (включительно), и сказать, что вы Дон'т хотите, чтобы сделать математику самостоятельно, и печатать их в stdout:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

Если вам нужно обработать тоже эти строки, Это's не очень удобно хранить пустую строку. В этом случае используйте опцию (отделка):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

Можно есть функция сделать это для вас:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

Нет внешних команд, только Баш примитивы!

Комментарии (0)

Вы также можете использовать СЭД печать и бросил:

sed -n '10{p;q;}' file   # print line 10
Комментарии (3)

Для этого можно также использовать Perl:

perl -wnl -e '$.== NUM && print && exit;' some.file
Комментарии (0)

Самое быстрое решение для больших файлов всегда хвост|голову, при условии, что два расстояния:

  • от начала файла до стартовой линии. Назовем это S
  • расстояние от последней строки до конца файла. Будь то е

известны. Тогда мы могли бы использовать это:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

сколько всего количество необходимых линий.

Более подробно в https://unix.stackexchange.com/a/216614/79743

Комментарии (1)

Все вышеперечисленные ответы прямо ответить на вопрос. Но здесь's не менее прямого решения, но потенциально более важная идея, к размышлениям.

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

Реальное решение-это иметь индекс, например, в начале файла, с указанием должностей, где линии начинаются. Вы могли бы использовать формат базы данных, или просто добавить таблицу в начале файла. Также можно создать отдельный индексный файл, чтобы сопровождать ваш большой текстовый файл.

например, можно создать список символьных позиций строки:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

затем читать с "хвоста", который на самом деле `стремиться непосредственно к соответствующей точке в файл!

например, чтобы получить линию 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Это может не работать с 2-байтов многобайтовых символов, поскольку awk-это символа "осведомленным", а хвост не.
  • Я не'т протестировали это на большой файл.
  • См. Также этот ответ.
  • Как вариант - разбить файл на более мелкие файлы!
Комментарии (0)

Как отклик на CaffeineConnoisseur'ы очень полезно бенчмаркинг ответ... мне было интересно, как быстро 'карты' метода по сравнению с другим (как это было'т проверено), поэтому я попробовал быстрый-и-грязный скорость сравнения себя у меня есть Баш 4 удобно. Кинул в тест от "хвоста | головы" и способ (а не голова | хвост) упомянул в одном из комментариев на верхний ответ, пока я был в его, как люди поют его похвалы. Я не'т иметь ничего почти размер используемого вопросами и ответами; лучшее, что я мог найти в короткие сроки была родословная файла 14М (длинные строки, разделенных пробелами, просто под 12000 строк).

Короткая версия: Map-файла появляется быстрее, чем разрез метод, но медленнее, чем все остальное, поэтому я'д называть его пустышкой. хвост | голову, ото, похоже, это может быть самый быстрый, хотя с файлом такого размера разница не так высоки по сравнению с СЭД.

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

Надеюсь, что это помогает!

Комментарии (0)

Используя то, что другие упомянули, я хотела, чтобы это было быстро & функция Денди в моей оболочки Bash.

Создать файл: ~/.функции

Добавить к нему содержание:

Гэтлину() { строка=$1 СЭД $линия&#39;м;д&#39; $2 }

Затем добавьте это в ваш~/.файл`:

источник ~/.функции`

Теперь, когда вы открываете новое окно в bash, вы можете просто вызвать функцию так:

Гэтлину myfile.txt 441

Комментарии (0)

Если у вас есть несколько строк, разделенных символом \N (новая строка). Вы можете использовать 'вырезать' а также:

echo "$data" | cut -f2 -d$'\n'

Вы получите 2-ю строку из файла. `-Ф3 дает вам 3-й линии.

Комментарии (1)

Много уже хороших ответов. Я лично хожу с awk. Для удобства, если вы пользуетесь bash, просто добавить ниже в~/.файл`. И, при следующем входе в (Или если вы источник свой .файл после этого обновления), вы будете иметь новый отличный фильм "энное" и функцию доступной для трубы файлами через.

Выполнения той или положите его в ваш ~/.файл (если используется bash) и снова Баш (или выполнить источник ~/.bach_profile`)

# печать просто энное передается в линию ное () { на awk -vlnum=${1} &#39;НР==lnum {печать; выход}&#39;; }

Затем, чтобы использовать его, просто трубу через нее. Е. Г.:

$ да линия | Кэт -Н | энный 5 Линия 5

Комментарии (0)

Для печати энного через СЭД с переменной в качестве номера строки:

a=4
sed -e $a'q:d' file

Здесь ' е' флаг для добавления скрипта в командной строке.

Комментарии (1)

Я'ве положить некоторые из перечисленных выше ответов в короткие баш скрипт, который можно поместить в файл с именем get.sh и ссылка на/usr/местные/Бен/сделать` (или что другое имя вы предпочитаете).

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

Убедитесь, что он'ы исполняемый с

$ chmod +x get

Связать его, чтобы сделать его доступным на "путь" с

$ ln -s get.sh /usr/local/bin/get

Ответственно понравится!

П

Комментарии (0)