2012-07-03 10 views
50

Mam jeden plik z -| jako ogranicznik po każdej sekcji ... trzeba utworzyć osobne pliki dla każdej sekcji przy użyciu systemu Unix.Podziel jeden plik na wiele plików w oparciu o separator

przykład pliku wejściowego

wertretr 
ewretrtret 
1212132323 
000232 
-| 
ereteertetet 
232434234 
erewesdfsfsfs 
0234342343 
-| 
jdhg3875jdfsgfd 
sjdhfdbfjds 
347674657435 
-| 

Oczekiwany wynik w pliku 1

wertretr 
ewretrtret 
1212132323 
000232 
-| 

Oczekiwany wynik w pliku 2

ereteertetet 
232434234 
erewesdfsfsfs 
0234342343 
-| 

Oczekiwany wynik w pliku 3

jdhg3875jdfsgfd 
sjdhfdbfjds 
347674657435 
-| 
+1

Czy piszesz program, czy chcesz to zrobić za pomocą narzędzi wiersza poleceń? – rkyser

+1

przy użyciu narzędzi wiersza poleceń będzie lepiej .. – user1499178

+0

Można użyć awk, byłoby łatwo napisać program 3 lub 4 wiersz, aby to zrobić. Niestety jestem poza treningiem. –

Odpowiedz

6

Debian ma csplit, ale nie wiem, czy jest to wspólne dla wszystkich/większości/innych dystrybucji. Jeśli nie, chociaż, to nie powinno być zbyt trudne do wyśledzenia źródła i skompilować je ...

+1

Zgadzam się. Moje pudełko Debiana mówi, że csplit jest częścią gnu coreutils. Tak więc każdy system operacyjny Gnu, taki jak wszystkie dystrybucje Gnu/Linux, będzie go miał. Wikipedia wymienia także "The Single UNIX® Specification, Issue 7" na stronie csplit, więc podejrzewam, że ją masz. –

+2

Ponieważ ['csplit'] (http://pubs.opengroup.org/onlinepubs/9699919799/utilities/csplit.html) znajduje się w POSIX, oczekiwałbym, że będzie dostępny na zasadniczo wszystkich systemach uniksopodobnych. –

+1

Mimo, że csplit jest POISX, problem (wydaje się, że testowanie go w systemie Ubuntu siedzi przede mną) polega na tym, że nie ma oczywistego sposobu na użycie bardziej nowoczesnej składni regex. Porównaj: 'csplit --prefix gold-data -"/^ == * $/'vs' csplit --prefix gold-data - "/^= + $ /'. Przynajmniej GNU grep ma "-e". – new123456

0
cat file| (I=0; echo -n "">file0; while read line; do echo $line >> file$I; if [ "$line" == '-|' ]; then I=$[I+1]; echo -n "" > file$I; fi; done) 

i formatowany wersja:

#!/bin/bash 
cat FILE | (
    I=0; 
    echo -n"">file0; 
    while read line; 
    do 
    echo $line >> file$I; 
    if [ "$line" == '-|' ]; 
    then I=$[I+1]; 
     echo -n "" > file$I; 
    fi; 
    done; 
) 
+3

Jak zawsze ["kot" jest bezużyteczny] (http://www.iki.fi/era/unix/award.html). – tripleee

2

Można również użyć awk. Nie znam się na awk, ale poniższe rzeczy zdawały się działać dla mnie. Wygenerował part1.txt, part2.txt, part3.txt i part4.txt. Zauważ, że ostatni wygenerowany plik partn.txt jest pusty. Nie jestem pewien, jak to naprawić, ale jestem pewien, że można to zrobić przy odrobinie podkręcania. Jakieś sugestie?

awk_pattern file: komenda

BEGIN{ fn = "part1.txt"; n = 1 } 
{ 
    print > fn 
    if (substr($0,1,2) == "-|") { 
     close (fn) 
     n++ 
     fn = "part" n ".txt" 
    } 
} 

bash:

awk -f awk_pattern input.file

-1

Oto kod Perl, który zrobi coś

#!/usr/bin/perl 
open(FI,"file.txt") or die "Input file not found"; 
$cur=0; 
open(FO,">res.$cur.txt") or die "Cannot open output file $cur"; 
while(<FI>) 
{ 
    print FO $_; 
    if(/^-\|/) 
    { 
     close(FO); 
     $cur++; 
     open(FO,">res.$cur.txt") or die "Cannot open output file $cur" 
    } 
} 
close(FO); 
25
awk '{print $0 " -|"> "file" NR}' RS='-\\|' input-file 
+0

Jak dobrze działa to w przypadku naprawdę dużych plików (> 3 GB)? Nie znam się na awk. – rzetterberg

+0

Czy mógłbyś wyjaśnić różne części? Co to jest "RS"? Co to jest "NR"? –

+0

'RS' to separator rekordów, a to rozwiązanie wykorzystuje rozszerzenie gnu awk, które pozwala mu na więcej niż jedną literę. NR jest rekordową liczbą. Polecenie print wypisuje rekord, a następnie "- |" do pliku zawierającego numer rekordu w nazwie. –

61

Jedna liniowa, bez programowania. (Z wyjątkiem regexp itp)

csplit --digits=2 --quiet --prefix=outfile infile "/-|/+1" "{*}" 
+1

+1 - krótszy:' csplit -n2 -s -b plik wyjściowy plik "/ - |/+ 1" "{*}" ' – zb226

+19

@ zb226 Zrobiłem to długo, więc że żadne wyjaśnienie nie było potrzebne. –

+3

Proponuję dodać '--elide-empty-files', w przeciwnym razie na końcu pojawi się pusty plik. – luator

0

Jest to rodzaj problemu pisałem kontekstowe-split dla: http://stromberg.dnsalias.org/~strombrg/context-split.html

$ ./context-split -h 
usage: 
./context-split [-s separator] [-n name] [-z length] 
     -s specifies what regex should separate output files 
     -n specifies how output files are named (default: numeric 
     -z specifies how long numbered filenames (if any) should be 
     -i include line containing separator in output files 
     operations are always performed on stdin 
+0

Uh, wygląda to zasadniczo jak duplikat standardowego narzędzia 'csplit'. Zobacz [@ richard's answer] (http://stackoverflow.com/a/11314918/874188). – tripleee

+0

To jest rzeczywiście najlepsze rozwiązanie imo. Musiałem podzielić 98G mysql dump i csplit z jakiegoś powodu pożera całą moją pamięć RAM i zostaje zabity. Mimo że powinien tylko dopasować jedną linię w danym momencie. Nie ma sensu. Ten skrypt Pythona działa o wiele lepiej i nie zżera całego barana. –

5

Rozwiązałem nieco inny problem, gdzie plik zawierający linię z nazwa, pod którą powinien iść poniższy tekst. Ten kod pocztowy działa dla mnie:

#!/path/to/perl -w 

#comment the line below for UNIX systems 
use Win32::Clipboard; 

# Get command line flags 

#print ($#ARGV, "\n"); 
if($#ARGV == 0) { 
    print STDERR "usage: ncsplit.pl --mff -- filename.txt [...] \n\nNote that no space is allowed between the '--' and the related parameter.\n\nThe mff is found on a line followed by a filename. All of the contents of filename.txt are written to that file until another mff is found.\n"; 
    exit; 
} 

# this package sets the ARGV count variable to -1; 

use Getopt::Long; 
my $mff = ""; 
GetOptions('mff' => \$mff); 

# set a default $mff variable 
if ($mff eq "") {$mff = "-#-"}; 
print ("using file switch=", $mff, "\n\n"); 

while($_ = shift @ARGV) { 
    if(-f "$_") { 
    push @filelist, $_; 
    } 
} 

# Could be more than one file name on the command line, 
# but this version throws away the subsequent ones. 

$readfile = $filelist[0]; 

open SOURCEFILE, "<$readfile" or die "File not found...\n\n"; 
#print SOURCEFILE; 

while (<SOURCEFILE>) { 
    /^$mff (.*$)/o; 
    $outname = $1; 
# print $outname; 
# print "right is: $1 \n"; 

if (/^$mff /) { 

    open OUTFILE, ">$outname" ; 
    print "opened $outname\n"; 
    } 
    else {print OUTFILE "$_"}; 
    } 
+0

Czy możesz wyjaśnić, dlaczego ten kod działa? Mam podobną sytuację do tego, co tutaj opisałeś - wymagane nazwy plików wyjściowych są osadzone w pliku. Ale nie jestem zwykłym użytkownikiem perla, więc nie mogę zrozumieć tego kodu. – shiri

0

Następujące polecenie działa dla mnie. Mam nadzieję, że to pomoże. bash awk 'BEGIN{file = 0; filename = "output_" file ".txt"} /-|/ {getline; file ++; filename = "output_" file ".txt"}{print $0 > filename}' input

1

Oto Python 3 skrypt, który dzieli plik na kilka plików na podstawie nazwy pliku dostarczonych przez ograniczniki.Przykładowy plik wejściowy:

# Ignored 

######## FILTER BEGIN foo.conf 
This goes in foo.conf. 
######## FILTER END 

# Ignored 

######## FILTER BEGIN bar.conf 
This goes in bar.conf. 
######## FILTER END 

Oto scenariusz:

#!/usr/bin/env python3 

import os 
import argparse 

# global settings 
start_delimiter = '######## FILTER BEGIN' 
end_delimiter = '######## FILTER END' 

# parse command line arguments 
parser = argparse.ArgumentParser() 
parser.add_argument("-i", "--input-file", required=True, help="input filename") 
parser.add_argument("-o", "--output-dir", required=True, help="output directory") 

args = parser.parse_args() 

# read the input file 
with open(args.input_file, 'r') as input_file: 
    input_data = input_file.read() 

# iterate through the input data by line 
input_lines = input_data.splitlines() 
while input_lines: 
    # discard lines until the next start delimiter 
    while input_lines and not input_lines[0].startswith(start_delimiter): 
     input_lines.pop(0) 

    # corner case: no delimiter found and no more lines left 
    if not input_lines: 
     break 

    # extract the output filename from the start delimiter 
    output_filename = input_lines.pop(0).replace(start_delimiter, "").strip() 
    output_path = os.path.join(args.output_dir, output_filename) 

    # open the output file 
    print("extracting file: {0}".format(output_path)) 
    with open(output_path, 'w') as output_file: 
     # while we have lines left and they don't match the end delimiter 
     while input_lines and not input_lines[0].startswith(end_delimiter): 
      output_file.write("{0}\n".format(input_lines.pop(0))) 

     # remove end delimiter if present 
     if not input_lines: 
      input_lines.pop(0) 

Wreszcie oto jak go uruchomić:

$ python3 script.py -i input-file.txt -o ./output-folder/ 
0

Zastosowanie csplit jeśli masz.

Jeśli nie, ale masz Python ... nie używaj Perla.

Zakładając, że przykładowy plik nazywa się "samplein":

$ python -c "import sys 
for i, c in enumerate(sys.stdin.read().split('-|')): 
    open(f'out{i}', 'w').write(c)" < samplein 

Jeśli masz Python 3.5 lub niższe, nie można używać F-ciągi:

$ python -c "import sys 
for i, c in enumerate(sys.stdin.read().split('-|')): 
    open('out' + str(i), 'w').write(c)" < samplein 

i teraz:

$ ls out* 
out0 out1 out2 out3 
Powiązane problemy