Как я могу извлечь предопределенный диапазон строк из текстового файла в Unix?

532

У меня есть дамп SQL ~ 23000 строк, содержащий несколько баз данных данных. Мне нужно извлечь определенный раздел этого файла (то есть данные для одной базы данных) и поместить его в новый файл. Я знаю номера начала и конца строки нужных мне данных.

Кто-нибудь знает команду Unix (или серию команд), чтобы извлечь все строки из файла, скажем, между строками 16224 и 16482, а затем перенаправить их в новый файл?

Адам Дж. Форстер
источник
Поскольку вы упоминаете большие файлы, я предлагаю проверить комментарий stackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

Ответы:

793
sed -n '16224,16482p;16483q' filename > newfile

Из руководства пользователя sed :

p - Распечатайте пространство шаблона (к стандартному выводу). Эта команда обычно используется только в сочетании с параметром командной строки -n.

n - Если автоматическая печать не отключена, распечатайте пространство шаблона, затем, независимо от этого, замените пространство шаблона следующей строкой ввода. Если входных данных больше нет, sed выходит без обработки каких-либо команд.

q - Выход sedбез обработки каких-либо дополнительных команд или ввода. Обратите внимание, что текущее пространство шаблона печатается, если автоматическая печать не отключена с опцией -n.

а также

Адреса в сценарии sed могут быть в любой из следующих форм:

номер Указание номера строки будет соответствовать только этой строке на входе.

Диапазон адресов можно указать, указав два адреса, разделенных запятой (,). Диапазон адресов совпадает со строками, начиная с которых совпадает первый адрес, и продолжается до совпадения второго адреса (включительно).

boxxar
источник
3
Мне было любопытно, если это изменяет оригинальный файл. Я сделал резервную копию на всякий случай, и похоже, что это НЕ изменило оригинал, как и ожидалось.
Энди Грофф,
@AndyGroff. Чтобы изменить файл на месте, используйте параметр "-i". В противном случае он не изменит файл.
youri
175
Если, как и я, вам нужно сделать это для ОЧЕНЬ большого файла, это поможет, если вы добавите команду выхода в следующую строку. Тогда это sed -n '16224,16482p;16483q' filename. В противном случае sed продолжит сканирование до конца (или, по крайней мере, моя версия).
WDS
7
@MilesRout люди, кажется, спрашивают, "почему downvote?" довольно часто, возможно, вы имеете в виду «мне все равно» вместо «никого не волнует»
Марк
1
@wds - Ваш комментарий заслуживает ответа, который поднимается наверх. Это может иметь значение между днем ​​и ночью.
sancho.s ReinstateMonicaCellio
203
sed -n '16224,16482 p' orig-data-file > new-file

Где 16224,16482 - номер начальной и конечной строки включительно. Это 1-индексированный. -nподавляет эхо ввода как вывод, который вам явно не нужен; числа указывают диапазон строк, на которых должна работать следующая команда; команда pвыводит соответствующие строки.

JXG
источник
7
Для больших файлов указанная выше команда продолжит просмотр всего файла после того, как будет найден нужный диапазон. Есть ли способ, чтобы sed прекратил обработку файла после вывода диапазона?
Гари
39
Ну, из ответа здесь , кажется , что остановка в конце диапазона может быть достигнуто с: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Гари
5
Зачем вам ставить в ненужном месте, а затем придется цитировать? (Конечно, создание ненужных проблем и их решение - это сущность половины информатики, но я имею в виду, помимо этой причины ...)
Kaz
92

Довольно просто, используя голову / хвост:

head -16482 in.sql | tail -258 > out.sql

используя sed:

sed -n '16482,16482p' in.sql > out.sql

используя awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql
Manveru
источник
1
Второй и третий варианты в порядке, но первый медленнее, чем многие другие, потому что он использует 2 команды, где 1 достаточно. Это также требует вычисления, чтобы получить правильный аргумент tail.
Джонатан Леффлер
3
Стоит отметить, что для сохранения тех же номеров строк, что и в вопросе, команда sed должна быть, sed -n 16224,16482p' in.sql >out.sqlа команда awk должна бытьawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz
3
Также стоит знать, что в случае с первым примером head -16482 in.sql | tail -$((16482-16224)) >out.sqlвычисление сводится к bash
sibaz
1
Первый с головой и хвостом WAYYYY быстрее на больших файлах, чем версия sed, даже с добавленной q-опцией. мгновенная версия head и sed версия I Ctrl-C через минуту ... Спасибо
Мияги
2
Можно также использовать tail -n +16224для сокращения вычислений
SOFe
35

Вы можете использовать 'vi' и затем следующую команду:

:16224,16482w!/tmp/some-file

В качестве альтернативы:

cat file | head -n 16482 | tail -n 258

РЕДАКТИРОВАТЬ: - Просто чтобы добавить объяснение, вы используете head -n 16482 для отображения первых 16482 строк, а затем с помощью tail -n 258, чтобы получить последние 258 строк из первого вывода.

Марк Янссен
источник
2
И вместо vi вы можете использовать ex, то есть vi без интерактивной консоли.
Тадеуш А. Кадлубовски
1
Вам не нужна catкоманда; headможете прочитать файл напрямую. Это медленнее, чем многие альтернативы, потому что он использует 2 (3, как показано) команды, где 1 достаточно.
Джонатан Леффлер
1
@JonathanLeffler Вы совершенно не правы. Это невероятно быстро. Я извлекаю 200k строк, около 1G, из файла 2G с 500k строк за несколько секунд (без cat). Другие решения требуют как минимум несколько минут. Также кажется, что самая быстрая вариация на GNU tail -n +XXX filename | head XXX.
Антонис Христофидес
28

Есть еще один подход с awk:

awk 'NR==16224, NR==16482' file

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

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Федорки "ТАК прекратить вредить"
источник
2
1+ для сохранения времени выполнения и ресурсов с помощью print; exit. Спасибо !
Берни Райтер
Небольшое упрощение 2-го примера:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Робин А. Мид
Это ярко, спасибо @ RobinA.Meade! Я редактировал вашу идею в посте
fedorqui «SO прекратить вредить»
17
perl -ne 'print if 16224..16482' file.txt > new_file.txt
mmaibaum
источник
9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2
Cetra
источник
6
cat dump.txt | head -16224 | tail -258

должен сделать свое дело. Недостатком этого подхода является то, что вам нужно выполнить арифметику, чтобы определить аргумент для tail и учесть, хотите ли вы, чтобы между ними была добавлена ​​конечная строка или нет.

JP Lodine
источник
4
Вам не нужна catкоманда; headможете прочитать файл напрямую. Это медленнее, чем многие альтернативы, потому что он использует 2 (3, как показано) команды, где 1 достаточно.
Джонатан Леффлер
@JonathanLeffler Этот ответ легче всего прочитать и запомнить. Если бы вы действительно заботились о производительности, вы бы вообще не использовали оболочку. Хорошей практикой является позволить отдельным инструментам посвятить себя определенной задаче. Кроме того, «арифметика» может быть решена с помощью | tail -$((16482 - 16224)).
Йети
6

Стоя на плечах boxxar, мне нравится это:

sed -n '<first line>,$p;<last line>q' input

например

sed -n '16224,$p;16482q' input

В $означает «последняя строка», поэтому первая команда делает sedпечать всех строк , начиная с линии , 16224а вторая команда позволяет sedвыйти после печати строки 16428. (Добавление 1к q-range в растворе boxxar по - видимому, не требуется.)

Мне нравится этот вариант, потому что мне не нужно указывать номер конечной строки дважды. И я измерил, что использование $не оказывает вредного влияния на производительность.

Тильман Фогель
источник
3

Быстро и грязно:

head -16428 < file.in | tail -259 > file.out

Вероятно, не лучший способ сделать это, но это должно работать.

Кстати: 259 = 16482-16224 + 1.

jan.vdbergh
источник
Это медленнее, чем многие альтернативы, потому что он использует 2 команды, где 1 достаточно.
Джонатан Леффлер
3

Я написал программу на Haskell под названием splitter, которая делает именно это: прочитайте мою публикацию в блоге .

Вы можете использовать программу следующим образом:

$ cat somefile | splitter 16224-16482

И это все, что нужно сделать. Вам понадобится Haskell для его установки. Просто:

$ cabal install splitter

И вы сделали. Я надеюсь, что вы найдете эту программу полезной.

Роберт Массайоли
источник
Читает splitterтолько со стандартного ввода? В некотором смысле это не имеет значения; catкоманда является излишней , имеет ли он или нет. Либо используйте, splitter 16224-16482 < somefileлибо (если он принимает аргументы имени файла) splitter 16224-16482 somefile.
Джонатан Леффлер
3

Даже мы можем сделать это, чтобы проверить в командной строке:

cat filename|sed 'n1,n2!d' > abc.txt

Например:

cat foo.pl|sed '100,200!d' > abc.txt
Чинмой Падхи
источник
6
Вам не нужна catкоманда ни в одном из них; sedотлично способен читать файлы самостоятельно, или вы можете перенаправить стандартный ввод из файла.
Джонатан Леффлер
3

Используя ruby:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf
Карл Блейкли
источник
2

Я собирался опубликовать трюк с головой / хвостом, но на самом деле я бы просто запустил Emacs. ;-)

  1. esc- xGoto-Line ret16224
  2. знак ( ctrl- space)
  3. esc- xGoto-Line ret16482
  4. esc-w

откройте новый выходной файл, ctl-y сохраните

Давайте посмотрим, что происходит.

sammyo
источник
4
По моему опыту, Emacs не очень хорошо работает с очень большими файлами.
Грег Мэттс
Можете ли вы выполнить это как действие по сценарию, или это только интерактивная опция?
Джонатан Леффлер
2

Я хотел бы использовать:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR содержит номер записи (строки) строки, читаемой из файла.

Paddy3118
источник
2

Я хотел сделать то же самое из сценария, использующего переменную, и достиг этого, поместив кавычки вокруг переменной $, чтобы отделить имя переменной от p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Я хотел разделить список на отдельные папки, нашел начальный вопрос и ответил на полезный шаг. (команда split не является опцией на старой ОС, я должен перенести код на).

KevinY
источник
1

Я написал небольшой bash-скрипт, который вы можете запустить из командной строки, если вы обновите свой PATH, включив в него его каталог (или вы можете поместить его в каталог, который уже содержится в PATH).

Использование: $ pinch filename start-line end-line

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0
DrNerdfighter
источник
1
Это медленнее, чем многие альтернативы, потому что он использует 2 команды, где 1 достаточно. Фактически, он читает файл дважды из-за wcкоманды, которая тратит пропускную способность диска, особенно на гигабайтные файлы. Во всех отношениях это хорошо документировано, но это также излишнее инженерное решение.
Джонатан Леффлер
1

Это может работать для вас (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

или воспользоваться bash:

sed -n $'16224,16482w newfile\n16482q' file
Potong
источник
1

Используя ed:

ed -s infile <<<'16224,16482p'

-sподавляет диагностический вывод; фактические команды в строке здесь. В частности, 16224,16482pзапускает команду p(печать) в нужном диапазоне адресов строк.

Бенджамин В.
источник
0

Работа с ключами -n в ответах. Вот другой способ, если вы склонны.

cat $filename | sed "${linenum}p;d";

Это делает следующее:

  1. передать содержимое файла (или подать текст так, как вы хотите).
  2. sed выбирает данную строку, печатает ее
  3. Для удаления строк требуется d, иначе sed будет считать, что все строки будут в конечном итоге напечатаны. т. е. без d вы получите все строки, напечатанные выбранной строкой, напечатанные дважды, потому что у вас есть часть $ {linenum} p, запрашивающая ее печать. Я почти уверен, что -n в основном делает то же самое, что и d здесь.
ThinkBonobo
источник
3
записка cat file | sedлучше написана какsed file
fedorqui «ТАК прекратить причинять вред»
Кроме того, это просто печатает строку, тогда как вопрос о диапазоне их.
Федорки "ТАК прекратить причинять вред"
0

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

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Напечатает строку [Данные] и остальные. Если вы хотите, чтобы текст из строки 1 соответствовал шаблону, введите: sed -n '1, / Data / p' myfile. Кроме того, если вы знаете два шаблона (лучше быть уникальными в вашем тексте), как начальная, так и конечная линия диапазона могут быть указаны с совпадениями.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Кемин Чжоу
источник