Як можна розділити текстовий файл на кілька текстових файлів?


16

У мене є текстовий файл, entry.txtякий містить таке:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Я хотів би розділити його на три текстових файлів: entry1.txt, entry2.txt, entry3.txt. Їх зміст такий.

entry1.txt :

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt :

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt :

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

Іншими словами, [символ вказує, що новий файл повинен починатися. Записи ( [ entry*]де *ціле число) завжди в числовому порядку і є послідовними цілими числами, починаючи від 1 до N (у моєму фактичному вхідному файлі, N = 200001).

Чи я можу виконати автоматичне розбиття текстових файлів у bash? Мій фактичний вклад entry.txtнасправді містить 200,001 запис.

Відповіді:


11

І ось приємний, простий, однотонний лайнер:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

Це буде працювати для будь-якого розміру файлу, незалежно від кількості рядків у кожному записі, поки буде виглядати кожен заголовок запису [ blahblah blah blah ]. Помітьте простір безпосередньо після відкриття [та безпосередньо перед закриттям ].


ПОЯСНЕННЯ:

awkі gawkчитати вхідний файл рядок. Коли читається кожен рядок, його вміст зберігається у $0змінній. Тут ми gawkхочемо зіставити що-небудь у квадратних дужках та зберегти його збіг у масив k.

Отже, кожного разу, коли регулярний вираз буде збігатися, тобто для кожного заголовка у вашому файлі, k [1] матиме відповідну область рядка. А саме, "entry1", "entry2" або "entry3" або "entryN".

Нарешті, ми друкуємо кожен рядок у файлі під назвою <whatever value k currently has>.txt, тобто entry1.txt, entry2.txt ... entryN.txt.

Цей спосіб буде набагато швидшим, ніж perl для великих файлів.


+1 приємно. Вам не потрібно для matchвступу: /^\[/ { name=$2 }повинно вистачити.
Тор

Дякую @Thor. Ваша пропозиція є правильною для описаного випадку, але вона передбачає, що в назві запису ніколи немає місця. Ось чому я використав приклад [ blahblah blah blah ]у своїй відповіді.
тердон

Ах, я пропустив трохи про місця, розділені місцями. Ви також можете розмістити тих, кого немає FS, наприклад -F '\\[ | \\]'.
Тор

@terdon Мені дуже подобаються ці короткі рішення, на жаль, я зазвичай не можу їх узагальнити під мої потреби. Не могли б ви мені подати руку? У моєму файлі є рядки, що починаються з #S x, де x - 1, 2 або 3 цифри. Достатньо лише зберегти їх у x.dat. Я спробував: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtі деякі варіанти цього.
mikuszefski

Зрозумів, що gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtзробив свою справу. 2Хоча не дуже добре розумієш номер масиву .
mikuszefski

17

За допомогою csplit від GNU coreutils ( невбудований Linux, Cygwin):

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

У вас з’явиться додатковий порожній файл entry0.txt(містить частину перед першим заголовком).

У стандартному csplit відсутній {*}невизначений повторювач та -bможливість вказати формат суфіксу, тому для інших систем вам доведеться спочатку рахувати кількість розділів та перейменовувати вихідні файли згодом.

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

Я вважаю, що csplit трохи вигадливий час від часу, але неймовірно корисний, коли мені хочеться робити подібні речі.
ixtmixilix

10

У Perl це можна зробити набагато простіше:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

Ось короткий awk однолінійний:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

Як це працює?

  • /^\[/ відповідає лініям, що починаються з лівої квадратної дужки, і
  • {ofn=$2 ".txt"}Встановлює змінну для другого слова з обмеженим пробілом як назва нашого вихідного файла. Потім,
  • ofn це умова, що оцінюється як істинне, якщо вказана змінна (таким чином, ігноруючи рядки перед першим заголовком)
  • {print > ofn} перенаправляє поточний рядок до вказаного файлу.

Зауважте, що всі пробіли в цьому сценарії awk можна видалити, якщо компактність робить вас щасливими.

Зауважте також, що вищезазначений сценарій дійсно потребує заголовках розділів, щоб вони мали пробіли навколо, а не в них. Якщо ви хочете мати можливість обробляти заголовки розділів як, [foo]і [ this that ]вам знадобиться дещо більше коду:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

Для цього використовується sub()функція awk, щоб знімати провідні та кінцеві квадратні дужки-плюс-пробіли. Зауважте, що за стандартної поведінки awk це зруйнує пробіл (роздільник поля) в єдиний простір (тобто [ this that ]збережено в "this that.txt"). Якщо важливим є підтримка вихідного пробілу у вихідних назви файлів, можна експериментувати, встановивши FS.


2

Це можна зробити з командного рядка в python у вигляді:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

Це дещо неохайний, але легко зрозумілий спосіб зробити це: використовувати, grep -l '[ entry ]' FILENAMEщоб змусити розділити номери рядків на [запис]. Використовуйте комбінацію з голови та хвоста, щоб отримати потрібні шматочки.

Як я вже сказав; це не дуже, але легко зрозуміти.


2

Як щодо використання awk з [роздільником записів та пробілом як роздільником поля. Це дає нам можливість легко вводити дані у файл, як $0там, де він повинен повернути видалене ведуче [і ім'я файлу як $1. Тоді нам залишається лише обробляти особливий випадок першого запису, який порожній. Це дає нам:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

Відповідь Тердона працює для мене, але мені потрібно було використовувати гаук, а не awk. Посібник з gawk (пошук 'match (') пояснює, що аргумент масиву в match () - це розширення gawk. відповідь:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

Ось рішення perl. Цей скрипт визначає [ entryN ]рядки і відповідно змінює вихідний файл, але не перевіряє, аналізує і не обробляє дані в кожному розділі, він просто друкує рядок введення у вихідний файл.

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

Привіт, я написав цей простий сценарій, використовуючи рубін, щоб вирішити вашу проблему

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

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

ruby split.rb < entry.txt

Я перевірив це, і він працює чудово ..


1

Я віддаю перевагу csplit варіант, але в якості альтернативи ось рішення GNU awk:

розбір

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

Виконайте це так:

gawk -f parse.awk entry.txt

1
FWIW, RTзмінна, як видається, має специфічну оцінку. Це рішення не працює для мене, використовуючи awk FreeBSD.
ghoti

@ghoti: Так, я мав би це сказати. Я це включив у відповідь зараз. Спасибі.
Тор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.