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


531

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

Хтось знає команду Unix (або серію команд), щоб витягнути всі рядки з файлу між рядками 16224 і 16482, а потім перенаправити їх у новий файл?


Так як ви вже великі файли, я пропоную перевірити коментар stackoverflow.com/questions/83329 / ...
sancho.s ReinstateMonicaCellio

Відповіді:


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

З посібника з sed :

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

n - Якщо автоматичний друк не вимкнено, надрукуйте простір шаблону, а потім, незалежно від цього, замініть простір шаблону наступним рядком введення. Якщо входу більше немає, sed виходить, не обробляючи більше команд.

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

і

Адреси в сценарії sed можуть бути в будь-якій з наступних форм:

номер Зазначення номера рядка буде відповідати лише тому рядку на вході.

Діапазон адрес можна вказати, вказавши дві адреси, розділені комою (,). Діапазон адрес відповідає рядкам, починаючи з того, де збігається перша адреса, і триває до збігу другої адреси (включно).


3
Мені було цікаво, якщо це змінює оригінальний файл. Я створив резервну копію про всяк випадок, і, здається, це НЕ змінило оригінал, як очікувалося.
Енді Грофф

@AndyGroff. Щоб змінити файл на місці, використовуйте параметр "-i". Інакше файл не змінить.
youri

175
Якщо, як я, вам потрібно зробити це в ДУЖЕ великому файлі, це допоможе, якщо ви додасте команду виходу з наступного рядка. Тоді це sed -n '16224,16482p;16483q' filename. Інакше sed триматиме сканування до кінця (або, принаймні, моя версія).
вд

7
@MilesRout люди, здається, запитують "навіщо знищення?" досить часто, можливо, ви маєте на увазі "Мені все одно" замість "нікого не байдуже"
Марк

1
@wds - Ваш коментар заслуговує на відповідь, яка піднімається на вершину. Це може змінити день і ніч.
sancho.s ReinstateMonicaCellio

203
sed -n '16224,16482 p' orig-data-file > new-file

Де 16224,16482 - номер початкової лінії та номер кінцевої лінії, включно. Це 1-індексований. -nпригнічує відлуння введення як виводу, чого ви, очевидно, не хочете; цифри вказують на діапазон рядків, щоб змусити працювати наступну команду; команда pвиводить відповідні рядки.


7
На великих файлах вищевказана команда продовжує ходити весь файл після того, як буде знайдено потрібний діапазон. Чи є спосіб зупинити обробку файлу після виходу діапазону?
Гері

39
Ну, з відповіді тут , здається , що зупинка в кінці діапазону може бути досягнуто з: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Гарі

5
Чому б ви поставили у непотрібному просторі, а потім мали б цитувати? (Звичайно, створення непотрібних проблем і їх вирішення - це суть половини інформатики, але я маю на увазі, крім цієї причини ...)
Каз

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

1
Другий і третій параметри в порядку, але перший - повільніше, ніж багато альтернативних варіантів, оскільки він використовує 2 команди, де достатньо 1. Також потрібні обчислення, щоб отримати правильний аргумент tail.
Джонатан Леффлер

3
Варто зауважити, що для того, щоб зберігати ті самі номери рядків, що і питання, повинна бути команда sed, а команда sed -n 16224,16482p' in.sql >out.sqlawk повинна бутиawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz

3
Також варто знати, що у випадку з першим прикладом head -16482 in.sql | tail -$((16482-16224)) >out.sqlобчислення залишається на баш
sibaz

1
Перший з головою та хвостом WAYYYY швидше на великих файлах, ніж версія sed, навіть із додаванням опції q. головна версія миттєвої та sed версії I Ctrl-C через хвилину ... Дякую
Miyagi

2
Також можна використовувати tail -n +16224для зменшення обчислень
SOFe

35

Ви можете використовувати "vi", а потім таку команду:

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

Як варіант:

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

EDIT: - Для того, щоб додати пояснення, ви використовуєте head -n 16482 для відображення перших 16482 рядків, потім використовуйте хвостик -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 "ТАК перестаньте шкодити"


9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2

6
cat dump.txt | head -16224 | tail -258

повинен зробити трюк. Недоліком цього підходу є те, що вам потрібно виконати арифметику, щоб визначити аргумент для хвоста та врахувати, хочете ви, щоб «між» включав кінцеву лінію чи ні.


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

Напевно, це не найкращий спосіб зробити це, але це має працювати.

BTW: 259 = 16482-16224 + 1.


Це повільніше, ніж багато альтернативних варіантів, оскільки для цього використовується 2 команди, де достатньо 1.
Джонатан Леффлер

3

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

Ви можете використовувати програму наступним чином:

$ 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ідеально здатний читати файли самостійно, або ви можете перенаправити стандартний вхід з файлу.
Джонатан Леффлер


2

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

  1. esc- xгото-лінія ret16224
  2. позначити ( ctrl- space)
  3. esc- гото x-лінія ret16482
  4. esc-w

відкрити новий вихідний файл, зберегти ctl-y

Давайте подивимося, що відбувається.


4
На мій досвід Emacs не дуже добре працює на дуже великих файлах.
Грег Маттес

Чи можете ви виконати це як сценарій дії, або це лише інтерактивний варіант?
Джонатан Леффлер

2

Я б використав:

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

FNR містить номер запису (рядка) рядка, який читається з файлу.


2

Я хотів зробити те ж саме зі сценарієм, використовуючи змінну, і досягнув цього, поставивши лапки навколо змінної $, щоб відокремити ім'я змінної від p:

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

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


1

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

Використання: $ pinch кінцевий рядок назви файла

#!/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

1
Це повільніше, ніж багато альтернативних варіантів, оскільки для цього використовується 2 команди, де достатньо 1. Фактично він читає файл двічі через wcкоманду, яка витрачає пропускну здатність диска, особливо на гігабайтні файли. Це є всілякими документами, але це також надмірно інженерно.
Джонатан Леффлер

1

Це може допомогти вам (GNU sed):

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

або скориставшись басом:

sed -n $'16224,16482w newfile\n16482q' file

1

Використовуючи ed:

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

-sпригнічує діагностичний вихід; фактичні команди знаходяться у рядку тут. Зокрема, 16224,16482pзапускає команду p(print) у потрібному діапазоні адрес рядка.


0

-N у прийнятті відповідей працює. Ось ще один спосіб, якщо ви схильні.

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

Це робить наступне:

  1. вводити вміст файлу (або подавати в тексті, як тільки ви хочете).
  2. sed вибирає заданий рядок, друкує його
  3. d потрібно видалити рядки, інакше sed припустить, що всі рядки з часом будуть надруковані. тобто без d ви отримаєте всі рядки, надруковані вибраним рядком, надруковані двічі, оскільки у вас є $ {linenum} p частина, яка вимагає, щоб вона була надрукована. Я майже впевнений, що -n в основному робить те саме, що і d.

3
Примітка cat file | sedкраще написана якsed file
fedorqui "Так перестаньте шкодити"

Також це просто надрукує рядок, тоді як питання стосується їх діапазону.
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'. Крім того, якщо ви знаєте два шаблони (краще бути унікальним у вашому тексті), і початковий, і кінцевий рядок діапазону можна вказати зі збігами.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.