Bagaimana saya bisa membagi file teks menjadi beberapa file teks?

16

Saya memiliki file teks bernama entry.txtyang berisi yang berikut ini:

[ 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

Saya ingin membaginya menjadi tiga file teks: entry1.txt, entry2.txt, entry3.txt. Isinya sebagai berikut.

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

Dengan kata lain, [karakter menunjukkan file baru harus dimulai. Entri ( [ entry*], di mana *bilangan bulat) selalu dalam urutan numerik dan bilangan bulat berturut-turut mulai dari 1 hingga N (dalam file input aktual saya, N = 200001).

Apakah ada cara saya bisa menyelesaikan pemisahan file teks otomatis di bash? Input saya yang sebenarnya entry.txtsebenarnya berisi 200.001 entri.

Andrew
sumber

Jawaban:

11

Dan ini adalah one-liner bagus, sederhana, melongo:

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

Ini akan berfungsi untuk ukuran file apa pun , terlepas dari jumlah baris di setiap entri, asalkan setiap header entri terlihat [ blahblah blah blah ]. Perhatikan ruang setelah pembukaan [dan sesaat sebelum penutupan ].


PENJELASAN:

awkdan gawkmembaca file input baris demi baris. Ketika setiap baris dibaca, isinya disimpan dalam $0variabel. Di sini, kami menyuruh gawkmencocokkan apa pun dalam kurung siku, dan menyimpan koreknya ke dalam array k.

Jadi, setiap kali ekspresi reguler dicocokkan, yaitu, untuk setiap header di file Anda, k [1] akan memiliki wilayah yang cocok dengan baris tersebut. Yaitu, "entry1", "entry2" atau "entry3" atau "entryN".

Akhirnya, kami mencetak setiap baris menjadi file yang disebut <whatever value k currently has>.txt, yaitu entry1.txt, entry2.txt ... entryN.txt.

Metode ini akan jauh lebih cepat daripada perl untuk file yang lebih besar.

terdon
sumber
+1 bagus. Anda tidak perlu matchmasuk: /^\[/ { name=$2 }sudah cukup.
Thor
Terima kasih @Thor. Saran Anda benar untuk kasus yang dijelaskan, tetapi mengasumsikan tidak pernah ada spasi dalam nama entri. Itu sebabnya saya menggunakan contoh [ blahblah blah blah ]dalam jawaban saya.
terdon
Ah saya ketinggalan sedikit tentang entri yang dipisahkan ruang. Anda juga dapat mengakomodasi mereka dengan FS, misalnya -F '\\[ | \\]'.
Thor
@terdon Saya sangat suka solusi singkat ini, sayangnya saya biasanya gagal menyamaratakannya untuk kebutuhan saya. Bisakah Anda membantu saya? File saya memiliki garis yang dimulai dengan #S x, di mana x adalah angka 1, 2, atau 3 digit. Hanya menyimpannya ke x.dat sudah cukup. Saya mencoba: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txtdan beberapa variasi dari itu.
mikuszefski
Sudah gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txtberhasil. Namun, tidak terlalu memahami nomor array 2.
mikuszefski
17

Dengan csplit dari GNU coreutils (Linux yang tidak tertanam, Cygwin):

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

Anda akan berakhir dengan file kosong tambahan entry0.txt(berisi bagian sebelum tajuk pertama).

Csplit standar tidak memiliki {*}repeater tidak terbatas dan -bopsi untuk menentukan format suffix, jadi pada sistem lain Anda harus menghitung jumlah bagian terlebih dahulu dan mengganti nama file output setelahnya.

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
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Saya menemukan csplit agak aneh sesekali, tetapi sangat berguna ketika saya ingin melakukan hal semacam ini.
ixtmixilix
10

Dalam perl itu dapat dilakukan jauh lebih sederhana:

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

Berikut ini adalah satu kalimat pendek awk:

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

Bagaimana cara kerjanya?

  • /^\[/ cocok dengan garis yang dimulai dengan braket persegi kiri, dan
  • {ofn=$2 ".txt"}menetapkan variabel ke kata spasi putih-dibatasi kedua sebagai nama file output kami. Kemudian,
  • ofn adalah kondisi yang bernilai true jika variabel disetel (sehingga menyebabkan baris sebelum header pertama Anda diabaikan)
  • {print > ofn} mengalihkan garis saat ini ke file yang ditentukan.

Perhatikan bahwa semua ruang dalam skrip awk ini dapat dihapus, jika kekompakan membuat Anda bahagia.

Perhatikan juga bahwa skrip di atas benar-benar membutuhkan tajuk bagian untuk memiliki ruang di sekitarnya dan bukan di dalamnya. Jika Anda ingin dapat menangani header bagian seperti [foo]dan [ this that ], Anda memerlukan kode yang sedikit lebih banyak:

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

Ini menggunakan sub()fungsi awk untuk menghapus tanda kurung dan spasi spasi. Perhatikan bahwa per perilaku standar awk, ini akan menciutkan spasi (pemisah bidang) menjadi satu ruang tunggal (yaitu [ this that ]disimpan ke "this that.txt"). Jika mempertahankan spasi putih asli dalam nama file output Anda penting, Anda dapat bereksperimen dengan menetapkan FS.

ghoti
sumber
2

Ini dapat dilakukan dari baris perintah dengan python sebagai:

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)'
Paddy3118
sumber
2

Ini adalah cara yang agak kasar, tetapi mudah dipahami untuk melakukannya: gunakan grep -l '[ entry ]' FILENAMEuntuk mendapatkan nomor baris untuk dipecah pada [entri]. Gunakan kombinasi dari kepala dan ekor untuk mendapatkan potongan yang tepat.

Seperti yang saya katakan; itu tidak cantik, tetapi mudah untuk dipahami.

Sigurt Dinesen
sumber
2

Bagaimana dengan menggunakan awk dengan [sebagai pemisah catatan dan ruang sebagai pemisah bidang. Ini memberi kita dengan mudah data yang akan dimasukkan ke dalam file sebagai $0tempat ia harus meletakkan kembali lead yang dihapus [dan nama file sebagai $1. Kami kemudian hanya perlu menangani kasus khusus dari catatan 1 yang kosong. Ini memberi kita:

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

Jawaban terdon bekerja untuk saya, tetapi saya perlu menggunakan gawk, bukan awk. The pengguna melongo (mencari 'match (') menjelaskan bahwa argumen array dalam pertandingan () adalah ekstensi melongo. Mungkin itu tergantung pada Linux Anda menginstal dan awk / nawk / melongo versi Anda, tetapi pada mesin Ubuntu saya hanya sangat baik melongo ran Terdon ini menjawab:

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

Inilah solusi perl. Script ini mendeteksi [ entryN ]garis dan mengubah file output sesuai, tetapi tidak memvalidasi, mem-parsing atau memproses data di setiap bagian, ini hanya mencetak garis input ke file output.

#! /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);
cas
sumber
1

Hai saya menulis skrip sederhana ini menggunakan ruby ​​untuk menyelesaikan masalah Anda

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

Anda dapat menggunakannya dengan cara ini:

ruby split.rb < entry.txt

saya telah mengujinya, dan berfungsi dengan baik ..

Kokizzu
sumber
1

Saya lebih suka csplitopsi tetapi sebagai alternatif inilah solusi awk GNU:

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
}

Jalankan seperti ini:

gawk -f parse.awk entry.txt
Thor
sumber
1
FWIW, RTvariabel tampaknya spesifik-gawk. Solusi ini tidak berfungsi untuk saya menggunakan awk FreeBSD.
ghoti
@ Ghoti: Benar, saya seharusnya menyebutkan itu. Saya telah memasukkan itu dalam jawaban sekarang. Terima kasih.
Thor