Bir metin dosyasını birden çok metin dosyasına nasıl bölebilirim?


16

entry.txtAşağıdakileri içeren adlı bir metin dosyası var :

[ 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

Üç metin dosyaları bölmek istiyorum: entry1.txt, entry2.txt, entry3.txt. İçerikleri aşağıdaki gibidir.

giriş1.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

giriş2.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

Başka bir deyişle, [karakter yeni bir dosyanın başlaması gerektiğini gösterir. Girişler ( [ entry*]burada *bir tamsayıdır) her zaman sayısal sıradadır ve 1'den N'ye (ardışık gerçek dosyamda N = 200001) başlayan ardışık tamsayılardır.

Ben bash otomatik metin dosyası bölme başarmak için herhangi bir yolu var mı? Gerçek entry.txtgirdim aslında 200.001 giriş içeriyor.

Yanıtlar:


11

Ve işte hoş, basit, gawk bir astar:

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

Bu, her giriş üstbilgisi gibi göründüğü sürece, her girişteki satır sayısından bağımsız olarak herhangi bir dosya boyutu için çalışır [ blahblah blah blah ]. Açıklıktan hemen sonra [ve kapanmadan hemen önce boşluğa dikkat edin ].


AÇIKLAMA:

awkve gawkbir girdi dosyasını satır satır okuyun. Her satır okunduğunda, içeriği $0değişkene kaydedilir . Burada gawkköşeli parantez içindeki herhangi bir şeyi eşleştirmeyi ve eşleşmeyi diziye kaydetmeyi söylüyoruz k.

Yani, düzenli ifade her eşleştiğinde, yani dosyanızdaki her başlık için k [1] satırın eşleşen bölgesine sahip olacaktır. Yani, "giriş1", "giriş2" veya "giriş3" veya "girişN".

Son olarak, her satırı <whatever value k currently has>.txt, input1.txt, entry2.txt ... entryN.txt adlı bir dosyaya yazdırırız.

Bu yöntem daha büyük dosyalar için perl'den çok daha hızlı olacaktır .


+1 güzel. matchGirişe gerek yok : /^\[/ { name=$2 }yeterli olmalı.
Thor

Teşekkürler @Thor. Öneriniz açıklanan vaka için doğrudur, ancak girişin adında asla boşluk olmadığını varsayar. Bu yüzden cevabımda örneği kullandım [ blahblah blah blah ].
terdon

Ah, boşlukla ayrılmış girişler hakkında biraz özledim. Bunları FSörneğin -F '\\[ | \\]';
Thor

@terdon Bu kısa çözümleri gerçekten seviyorum, maalesef genellikle ihtiyaçlarıma göre genelleştiremiyorum. Yardım edebilir misin? Dosyamda #S xx ile 1, 2 veya 3 haneli bir sayı olan satırlar var . Sadece x.dat'a kaydetmek yeterli olacaktır. Denedim: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtve bunun bazı varyasyonları.
mikuszefski

O Got gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txthile yaptı. Ancak dizi numarasını 2çok iyi anlamıyorum .
mikuszefski

17

İle csplit GNU coreutils (Linux, Cygwin olmayan gömülü) den:

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

Sonunda boş bir dosya alırsınız entry0.txt(ilk başlıktan önceki bölümü içerir).

Standart csplit , {*}belirsiz tekrarlayıcıdan ve -bsonek biçimini belirleme seçeneğinden yoksundur , bu nedenle diğer sistemlerde önce bölüm sayısını saymanız ve daha sonra çıktı dosyalarını yeniden adlandırmanız gerekir.

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 arada bir biraz ilginç buluyorum, ama bu tür bir şey yapmak istediğinizde inanılmaz faydalı.
ixtmixilix

10

Perl'de çok daha basit yapılabilir:

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

9

İşte kısa bir awk tek astar:

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

Bu nasıl çalışıyor?

  • /^\[/ sol köşeli ayraçla başlayan satırlarla eşleşir ve
  • {ofn=$2 ".txt"}çıktı dosyası adımız olarak ikinci beyaz boşlukla ayrılmış kelimeye bir değişken belirler. Sonra,
  • ofn değişken ayarlanmışsa true olarak değerlendirilen bir koşuldur (böylece ilk başlığınızdan önceki satırların yok sayılmasına neden olur)
  • {print > ofn} geçerli satırı belirtilen dosyaya yönlendirir.

O Not tüm doluluk seni mutlu ediyorsa awk'nın komut alanların, kaldırılabilir.

Ayrıca, yukarıdaki komut dosyasının bölüm başlıkları içinde değil, etrafında boşluk olması gerektiğini unutmayın. Eğer gibi bölüm başlıkları idare edebilmek istiyorsa [foo]ve [ this that ], hiç bu kadar biraz daha fazla kod gerekir:

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

Bu, sub()önde gelen ve sondaki köşeli parantez artı artı boşlukları ayırmak için awk işlevini kullanır . Standart awk davranışı başına, bu işlemin boşluk alanını (alan ayırıcısı) tek bir boşluğa (yani [ this that ]kaydedildiği yer "this that.txt") daraltacağını unutmayın . Çıktı dosya adlarınızdaki orijinal boşluğu korumak önemliyse, FS ayarını deneyebilirsiniz.


2

Python'daki komut satırından şu şekilde yapılabilir:

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

Bu biraz kaba ama anlaşılması kolay bir yol: grep -l '[ entry ]' FILENAMEsatır numaralarını [giriş] 'e bölmek için kullanın. Doğru parçaları almak için kafa ve kuyruktan bir kombinasyon kullanın.

Dediğim gibi; hoş değil, ama anlaşılması kolay.


2

Awk ile [bir kayıt ayırıcı olarak ve alan ayırıcı olarak boşluk kullanmaya ne dersiniz ? Bu bize kolayca $0kaldırılacak lider [ve dosya adını geri koymak zorunda olduğu yere dosyaya koymak için veri verir $1. O zaman sadece 1. kaydın boş olan özel durumunu ele almamız gerekir. Bu bize şunları verir:

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

2

terdon'un cevabı benim için çalışıyor ama aww değil gawk kullanmam gerekiyordu. Gawk kılavuzu ( '(maçın') aramak maçında dizi argüman () bir gawk uzantısı olduğunu açıklar. Belki yüklemek için Linux bağlıdır ve awk / nawk / gawk versiyonları ama benim Ubuntu makinede sadece gawk koştu Terdon mükemmel üzerinde Cevap:

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

1

İşte bir perl çözümü. Bu komut dosyası [ entryN ]satırları algılar ve çıktı dosyasını buna göre değiştirir, ancak her bölümdeki verileri doğrulamaz, ayrıştırmaz veya işlemez, yalnızca girdi satırını çıktı dosyasına yazdırır.

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

Merhaba ben probleminizi çözmek için ruby ​​kullanarak bu basit script yazdı

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

şu şekilde kullanabilirsiniz:

ruby split.rb < entry.txt

test ettim, ve iyi çalışıyor ..


1

csplitSeçeneği tercih ediyorum ama alternatif olarak bir GNU awk çözümü:

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
}

Bu şekilde çalıştırın:

gawk -f parse.awk entry.txt

1
FWIW, RTdeğişken gawk'ye özgü gibi görünüyor. Bu çözüm benim için FreeBSD'nin awk kullanarak işe yaramıyor.
ghoti

@ghoti: Doğru, bundan bahsetmeliydim. Bunu şimdi cevaba ekledim. Teşekkürler.
Thor
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.