Как я могу разбить текстовый файл на несколько текстовых файлов?


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, где х - 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 условие, которое оценивается как true, если переменная установлена ​​(таким образом, строки перед вашим первым заголовком игнорируются)
  • {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чтобы разделить номера строк в [entry]. Используйте комбинацию с головы и хвоста, чтобы получить правильные фигуры.

Как я сказал; это не красиво, но легко понять.


2

Как насчет использования awk [в качестве разделителя записей и пробела в качестве разделителя полей. Это дает нам легко данные, которые будут помещены в файл, $0где он должен вернуть удаленное [ведение и имя файла как $1. Затем нам нужно обработать только особый случай 1-й записи, который пуст. Это дает нам:

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

2

Ответ Тердона работает для меня, но мне нужно было использовать gawk, а не awk. В руководстве gawk (поиск по запросу «match (») »объясняется, что аргумент массива в match () является расширением gawk. Возможно, это зависит от вашей установки Linux и ваших версий awk / nawk / gawk, но на моей машине с Ubuntu только gawk запускал отличную версию Terdon. ответ:

$ 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, чтобы решить вашу проблему

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

parse.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переменная, кажется, специфична для gawk. Это решение не работает для меня, используя awk FreeBSD.
Готи

@ ghoti: Хорошо, я должен был упомянуть это. Я включил это в ответ сейчас. Благодарю.
Тор
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.