Jak podzielić plik tekstowy na wiele plików tekstowych


4

Mam plik tekstowy o nazwie, entry.txtktóry zawiera następujące elementy:

[ 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

Chciałbym podzielić ją na trzy pliki tekstowe: entry1.txt, entry2.txt, entry3.txt. Ich zawartość jest następująca.

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

Innymi słowy, [znak wskazuje, że nowy plik powinien się rozpocząć.

Czy jest jakiś sposób na automatyczne dzielenie plików tekstowych? Moje ostateczne, rzeczywiste dane wejściowe entry.txtfaktycznie zawierają 200 001 wpisów.

Świetnie byłoby podzielić tekst na Windows lub Linux. Nie mam dostępu do komputera Mac. Dzięki!


wszystkie wpisy mają 7 linii?
Hamed

@hamed Oops, zapomniałem wspomnieć, że niestety wpisy nie mają wszystkich 7 linii.
Andrew

Odpowiedzi:


3

A oto ładny, prosty gawk one-liner:

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

Będzie to działać dla każdego rozmiaru pliku, niezależnie od liczby linii w każdym wpisie, o ile wygląda nagłówek każdego wpisu [ blahblah blah blah ]. Zwróć uwagę na miejsce tuż po otwarciu [i tuż przed zamknięciem ].


WYJAŚNIENIE:

awki gawkczytać plik wejściowy linia po linii. Gdy każdy wiersz jest czytany, jego zawartość jest zapisywana w $0zmiennej. Mówimy tutaj, aby awk dopasowywał wszystko w nawiasach kwadratowych i zapisywał dopasowanie w tablicy k.

Zatem za każdym razem, gdy wyrażenie regularne jest dopasowane, to znaczy dla każdego nagłówka w pliku, k [1] będzie miał dopasowany region linii. Mianowicie „entry1”, „entry2” lub „entry3” lub „entryN”. name=k[1]po prostu zapisuje wartość k [1] (dopasowanie) w nowej zmiennej name.

Na koniec drukujemy każdą linię do pliku o nazwie <whatever value k currently has>.txt, tj. Entry1.txt, entry2.txt ... entryN.txt.

Ta metoda będzie znacznie szybsza niż Perl dla większych plików.

Nie mogę za to ręczyć, ponieważ nigdy nie korzystałem z powłoki systemu Windows, ale jestem gotów się założyć, że będzie to znacznie szybsze. Gawk / awk są SZYBKIE.


Działa to z gawk, ale nie z awk (przynajmniej awk w domyślnym systemie Debian). Funkcja dopasowania awk dopuszcza tylko dwa parametry, więc twój przykład podaje błąd składniowy z awk.
speakr

4

W przypadku rozwiązania Windows wypróbuj ten skrypt PowerShell:

$Path = "D:\Scripts\PS\test"
$InputFile = (Join-Path $Path "log.txt")
$Reader = New-Object System.IO.StreamReader($InputFile)

While (($Line = $Reader.ReadLine()) -ne $null) {
    If ($Line -match "\[ (.+?) \]") {
        $OutputFile = $matches[1] + ".txt"
    }

    Add-Content (Join-Path $Path $OutputFile) $Line
}

Edytuj odpowiednio zmienne $Pathi $InputFile. Z pewnymi drobnymi modyfikacjami może również zaakceptować te informacje jako parametry wiersza poleceń lub możesz przekształcić je w funkcję.


3

Jeszcze inne awkrozwiązanie:

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
}

2

Następujący skrypt perla wykonuje to zadanie:

#! / usr / bin / perl

podczas gdy (<STDIN>) {
    if ($ _ = ~ m / ^ \ [(. +?) \] /) {
        $ f = 1 $;
        zamknij FH, jeśli powiedz (FH)! = -1;
        open FH, ">", "$ f.txt" lub die "nie można otworzyć pliku $ f: $! \ n";
    }
    wydrukuj FH $ _;
}
zamknij FH;

Uruchom skrypt w następujący sposób:

script.pl < entry.txt

Skrypt działa bez względu na to, ile sekcji wejściowych jest zawartych i jak długo sekcje są tak długie, jak tylko nagłówki sekcji wejściowych [ some text ].


Jeśli wolisz nieczytelny kod lub po prostu nie chcesz gdzieś przechowywać skryptu, możesz użyć tego pojedynczego polecenia:

perl -e 'while(<STDIN>){if($_=~/^\[ (.+?) \]/){close FH if tell FH!=-1;open FH,">","$1.txt"or die"$1.txt: $!";}print FH $_;}close FH;' < entry.txt

Nie potrzebujesz cat, możesz po prostu biegać script.pl test.txt.
terdon

@terdon Nie, jeśli używasz STDIN, nie możesz przekazać pliku tekstowego jako parametru. Jest jednak script.pl < test.txtlepsze niż używanie cat- odpowiednio zaktualizowałem swoją odpowiedź.
speakr

Masz rację, przepraszam. Jestem zbyt przyzwyczajony do używania, while(<>)który bierze plik wejściowy jako pierwszy argument.
terdon

2

Czy nie jest łatwiej używać istniejących poleceń? Nie wszystko potrzebuje nowego programu.

plik csplit / \ [/


Masz rację, csplitjest właściwym narzędziem do pracy. Musiałem dodać liczbę powtórzeń i zamienić argumenty, aby działało. Poniższy wiersza polecenia zbliżony do tego, co PO poprosił o: csplit -f entry -b '%d.txt' -z entry.txt '/^\[/' '{*}'.
Thor

Działa jednak csplittylko wtedy, gdy nazwa rekordu w pliku będzie zgodna ze entryXXwzorcem, ponieważ nie obsługuje ustawiania prefiksów zmiennych
Suncatcher
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.