Есть ли "канонический" способ сделать это? Я использую head -n | tail -1
, что делает трюк, но мне стало интересно, есть ли в Bash инструмент, который специально извлекает строку (или диапазон строк) из файла.
Под "каноническим" я имею в виду программу, основная функция которой заключается в этом.
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
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, вы можете проверить это:
У меня уникальная ситуация, когда можно тест решения, предложенные на данной странице, и поэтому я'м пишу этот ответ как консолидация предлагаемого решения с включенными раз для каждого.
Настройка
У меня 3.261 гигабайт текстовых данных файл с одну пару ключ-значение для каждой строки. Файл содержит 3,339,550,320 строк в целом и не поддается открытию в любом редакторе я пробовал, в том числе и мой ВИМ. Мне нужно, чтобы подмножество этого файла, чтобы исследовать некоторые из тех ценностей, которые я've обнаружил только начальную строку ~500,000,000.
Поскольку файл имеет очень много строк:
Мои сценарии-это решение, которое извлекает только одну строку из файла без чтения любой другой строке в файле, но я могу'т думаю, что я бы сделать это в 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
ООО
на Perl
|хвост
на awk
Строки 500,000,000
ООО
на Perl
голова|хвост
на awk
Строки 3,338,559,320
ООО
на Perl
голова|хвост
на awk
С в 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
Вау, все возможности!
Попробуйте это:
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
или поглазеть
).
Есть ли инструмент, который не только распечатать, что конкретной линии? Не один из стандартных инструментов. Однако, СЭД
- это, наверное, самый близкий и простой в использовании.
По моим расчетам, с точки зрения производительности и читабельности моя рекомендация:
хвоста -Н+Н | глава -1`
N-это номер строки, которую вы хотите. Например, хвоста -н+7 input.txt | руководитель -1` будет печатать 7-й строке файла.
хвоста -н+нбудет печатать все, начиная от строки
П, и
Head -1` заставит его остановиться после одной линии.
Руководитель -Н альтернатива | хвост -1
, пожалуй, немного более читабельным. Например, это будет печать 7-й строке:
глава -7 input.txt | хвост -1`
Когда речь заходит о производительности, нет большой разницы в меньших размерах, но он будет проигрывать в `хвосте | голове (сверху), когда файлы становятся огромными.
Топ-проголосовали-Current функциональности sed 'NUMq;д'
интересно знать, но я утверждаю, что это будет воспринято меньше людей из коробки, чем голова/хвост решение, и это также медленнее, чем хвост/голову.
В моих тестах, обе хвосты/головы превзошли версии-Current функциональности sed 'NUMq;д'
последовательно. В соответствии с другими критериями, которые были размещены. Трудно найти случай, когда хвосты/головы было очень плохо. Это тоже не удивительно, так как эти операции, которые вы ожидаете, чтобы быть сильно оптимизирован в современной системе Unix.
Чтобы получить представление о различиях в производительности, это число, которое я получаю огромный файл (9,3 г):
глава -Н | хвост -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
# print line number 52
sed '52!d' file
Этот вопрос меткам Баш, здесь'ы Баш (≥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[@]}"
}
Нет внешних команд, только Баш примитивы!
Для этого можно также использовать Perl:
perl -wnl -e '$.== NUM && print && exit;' some.file
Самое быстрое решение для больших файлов всегда хвост|голову, при условии, что два расстояния:
S
е
известны. Тогда мы могли бы использовать это:
mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"
сколько всего количество необходимых линий.
Более подробно в https://unix.stackexchange.com/a/216614/79743
Все вышеперечисленные ответы прямо ответить на вопрос. Но здесь'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
Как отклик на 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
Надеюсь, что это помогает!
Используя то, что другие упомянули, я хотела, чтобы это было быстро & функция Денди в моей оболочки Bash.
Создать файл: ~/.функции
Добавить к нему содержание:
Гэтлину() { строка=$1 СЭД $линия'м;д' $2 }
Затем добавьте это в ваш~/.файл`:
источник ~/.функции`
Теперь, когда вы открываете новое окно в bash, вы можете просто вызвать функцию так:
Гэтлину myfile.txt 441
Много уже хороших ответов. Я лично хожу с awk. Для удобства, если вы пользуетесь bash, просто добавить ниже в~/.файл`. И, при следующем входе в (Или если вы источник свой .файл после этого обновления), вы будете иметь новый отличный фильм "энное" и функцию доступной для трубы файлами через.
Выполнения той или положите его в ваш ~/.файл (если используется bash) и снова Баш (или выполнить источник ~/.bach_profile`)
# печать просто энное передается в линию ное () { на awk -vlnum=${1} 'НР==lnum {печать; выход}'; }
Затем, чтобы использовать его, просто трубу через нее. Е. Г.:
$ да линия | Кэт -Н | энный 5 Линия 5
Я'ве положить некоторые из перечисленных выше ответов в короткие баш скрипт, который можно поместить в файл с именем 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
Ответственно понравится!
П