Sınırlayıcıya göre bir dosyayı birden çok dosyaya bölün


88

Ben bir kez dosya -|olarak her bölüm sonunda sınırlayıcı ... unix kullanarak her bölüm için ayrı dosyalar oluşturmak zorunda.

girdi dosyası örneği

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

Dosya 1'de beklenen sonuç

wertretr
ewretrtret
1212132323
000232
-|

Dosya 2'de beklenen sonuç

ereteertetet
232434234
erewesdfsfsfs
0234342343
-|

Dosya 3'te beklenen sonuç

jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|

1
Bir program mı yazıyorsunuz yoksa bunu komut satırı yardımcı programlarını kullanarak mı yapmak istiyorsunuz?
rkyser

1
komut satırı yardımcı programlarının kullanılması tercih edilecektir ..
user1499178

Awk kullanabilirsiniz, bunu yapmak için 3 veya 4 satırlık bir program yazmak kolay olacaktır. Maalesef pratik yapmıyorum.
ctrl-alt-delor

Yanıtlar:


98

Tek satırlık, programlama yok. (regexp vb. hariç)

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

test edildi: csplit (GNU coreutils) 8.30

Apple Mac'te kullanımla ilgili notlar

"OS X kullanıcıları için, csplitbunun işletim sistemi ile birlikte gelen sürümünün çalışmadığını unutmayın. Sürümü coreutils'te isteyeceksiniz (Homebrew aracılığıyla kurulabilir), adı verilen gcsplit." - @Danial

"Eklemek gerekirse, OS X'in çalışması için sürümünü edinebilirsiniz (en azından High Sierra ile). Yalnızca csplit -k -f=outfile infile "/-\|/+1" "{3}"bağımsız değişkenleri biraz ayarlamanız gerekiyor. Çalışmıyor gibi görünen "{*}"özellikler ayırıcıların sayısı ve -kson bir ayırıcı bulamazsa tüm çıktı dosyalarının silinmesini önlemek için eklenmesi gerekiyor . Ayrıca isterseniz --digits, -nbunun yerine kullanmanız gerekir . " - @Pebbl


31
@ zb226 Uzun süre yaptım, bu yüzden açıklamaya gerek kalmadı.
ctrl-alt-delor

5
Eklemenizi öneririm --elide-empty-files, aksi takdirde sonunda boş bir dosya olacaktır.
luator

8
OS X kullanıcıları için, işletim sistemi ile birlikte gelen csplit sürümünün çalışmadığını unutmayın. Gcsplit adı verilen coreutils'teki (Homebrew aracılığıyla kurulabilen) sürümü isteyeceksiniz .
Daniel

10
Sadece parametrelerin ne anlama geldiğini merak edenler için: --digits=2çıktı dosyalarını numaralandırmak için kullanılan basamakların sayısını kontrol eder (2 benim için varsayılandır, bu yüzden gerekli değildir). --quietçıktıyı bastırır (ayrıca gerçekten gerekli değildir veya burada istenmez). --prefixçıktı dosyalarının önekini belirtir (varsayılan xx'tir). Böylece tüm parametreleri atlayabilir ve xx12.
Christopher K.

3
Eklemek için, OS X'in çalışmasını sağlayabilirsiniz (en azından High Sierra ile). Sadece argümanları biraz değiştirmen gerekiyor csplit -k -f=outfile infile "/-\|/+1" "{3}". Çalışmayan "{*}"özellikler, ayırıcıların sayısı konusunda spesifik olmalıydım ve -kson bir ayırıcı bulamazsa tüm çıkış dosyalarını silmekten kaçınmak için eklemem gerekiyordu. Ayrıca isterseniz bunun yerine --digitskullanmanız gerekir -n.
Pebbl

39
awk '{f="file" NR; print $0 " -|"> f}' RS='-\\|'  input-file

Açıklama (düzenlendi):

RSkayıt ayırıcısıdır ve bu çözüm, birden fazla karakter olmasına izin veren bir gnu awk uzantısı kullanır. NRkayıt numarasıdır.

Print deyimi bir kaydı ve ardından " -|"adında kayıt numarasını içeren bir dosyaya yazdırır .


1
RSkayıt ayırıcısıdır ve bu çözüm, birden fazla karakter olmasına izin veren bir gnu awk uzantısı kullanır. NR, kayıt numarasıdır. Print deyimi bir kayıt ve ardından "- |" yazdırır. Adında kayıt numarasını içeren bir dosyaya.
William Pursell

1
@rzetterbeg Bu, büyük dosyalarla iyi çalışmalıdır. awk, dosyayı her seferinde bir kayıt olarak işler, böylece yalnızca gerektiği kadar okur. Kayıt ayırıcısının ilk oluşumu dosyada çok geç ortaya çıkarsa, bir kaydın belleğe sığması gerektiğinden, bu bir bellek krizi olabilir. Ayrıca, RS'de birden fazla karakter kullanmanın standart awk olmadığına ama bunun gnu awk'de çalışacağına dikkat edin.
William Pursell

4
Benim için 3.3 GB'yi 31.728 saniyede paylaştı
Cleankod

3
@ccf Dosya adı yalnızca sağ tarafındaki dizedir >, böylece istediğiniz gibi oluşturabilirsiniz. örneğin,print $0 "-|" > "file" NR ".txt"
William Pursell

1
@AGrush Bu sürüme bağlıdır. Yapabilirsinawk '{f="file" NR; print $0 " -|" > f}'
William Pursell

7

Debian'da var csplit, ancak bunun tüm / çoğu / diğer dağıtımlarda ortak olup olmadığını bilmiyorum. Değilse, kaynağı bulmak ve derlemek çok zor olmamalı ...


1
Katılıyorum. Debian kutum, csplit'in gnu coreutils'in bir parçası olduğunu söylüyor. Dolayısıyla, tüm Gnu / Linux dağıtımları gibi herhangi bir Gnu işletim sistemi buna sahip olacaktır. Wikipedia ayrıca csplit sayfasında 'The Single UNIX® Specification, Issue 7'den bahsediyor, bu yüzden anladığından şüpheleniyorum.
ctrl-alt-delor

3
Yana csplitPOSIX'deki, ben aslında tüm sistemlerde Unix benzeri kullanılabilir olması beklenir.
Jonathan Leffler

1
Csplit POISX olsa da, sorun (önümde oturan Ubuntu sisteminde onunla bir test yapıyor gibi görünüyor) daha modern bir regex sözdizimi kullanmanın bariz bir yolu olmamasıdır. Karşılaştır: csplit --prefix gold-data - "/^==*$/ile csplit --prefix gold-data - "/^=+$/. En azından GNU grep'in var -e.
new123456

5

Dosyanın, aşağıdaki metnin gitmesi gereken adda bir satır içerdiği biraz farklı bir sorunu çözdüm. Bu perl kodu benim için hile yapıyor:

#!/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 "$_"};
  }

Lütfen bu kodun neden çalıştığını açıklar mısınız? Burada anlattıklarınıza benzer bir durumum var - gerekli çıktı dosyası adları dosyanın içine gömülüdür. Ama ben normal bir perl kullanıcısı değilim, bu yüzden bu kodu tam olarak anlayamıyorum.
shiri

Gerçek sığır eti son whiledöngüde. mffNormal ifadeyi satırın başında bulursa, satırın geri kalanını açmak ve yazmaya başlamak için dosya adı olarak kullanır. Hiçbir şeyi kapatmaz, bu nedenle birkaç düzine sonra dosya tutamaçları tükenir.
üçlü

Senaryonuz nihai önce kodun en kaldırarak artırmak olacağını whilehiç döngü ve anahtarlamawhile (<>)
tripleee

4

Aşağıdaki komut benim için çalışıyor. Umarım yardımcı olur.

awk 'BEGIN{file = 0; filename = "output_" file ".txt"}
    /-|/ {getline; file ++; filename = "output_" file ".txt"}
    {print $0 > filename}' input

1
Bu, genellikle birkaç düzine dosyadan sonra dosya tanıtıcıları tükenir. Düzeltme, closeyenisini başlattığınızda açıkça eski dosyaya yöneliktir.
üçlü

@tripleee nasıl kapatırsınız (acemi awk sorusu). Güncellenmiş bir örnek verebilir misiniz?
Jesper Rønn-Jensen

1
@ JesperRønn-Jensen Bu kutu, herhangi bir yararlı örnek için muhtemelen çok küçüktür, ancak temelde if (file) close(filename);yeni bir filenamedeğer atamadan önce .
üçlü

aah kapatmak için nasıl bulundu: ; close(filename). Gerçekten basit, ancak yukarıdaki örneği gerçekten düzeltir
Jesper Rønn-Jensen

1
@ JesperRønn-Jensen Bozuk bir komut dosyası sağladığınız için düzenlemenizi geri aldım. Başkalarının cevaplarında büyük olasılıkla önemli düzenlemelerden kaçınılmalıdır - ayrı bir cevabın gerekli olduğunu düşünüyorsanız , kendi yeni bir cevabınızı (belki bir topluluk wiki olarak ) göndermekten çekinmeyin .
üçlü

2

Awk da kullanabilirsiniz. Awk'a pek aşina değilim, ancak aşağıdakiler benim için işe yaradı. Part1.txt, part2.txt, part3.txt ve part4.txt oluşturdu. Bunun oluşturduğu son partn.txt dosyasının boş olduğunu unutmayın. Nasıl düzelttiğinden emin değilim, ama eminim biraz ince ayarlarla yapılabilir. Herhangi bir öneriniz var mı?

awk_pattern dosyası:

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

bash komutu:

awk -f awk_pattern input.file


2

Burada, sınırlayıcılar tarafından sağlanan dosya adına göre bir dosyayı birden çok dosyaya bölen bir Python 3 betiği var. Örnek girdi dosyası:

# Ignored

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

# Ignored

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

İşte senaryo:

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

Son olarak, işte nasıl çalıştıracağınız:

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

2

csplitVarsa kullanın .

Eğer yoksa, ama Python'unuz varsa ... Perl kullanmayın.

Dosyanın tembel okunması

Dosyanız tek seferde bellekte tutulamayacak kadar büyük olabilir - satır satır okumak tercih edilebilir. Girdi dosyasının "samplein" olarak adlandırıldığını varsayın:

$ python3 -c "from itertools import count
with open('samplein') as file:
    for i in count():
        firstline = next(file, None)
        if firstline is None:
            break
        with open(f'out{i}', 'w') as out:
            out.write(firstline)
            for line in file:
                out.write(line)
                if line == '-|\n':
                    break"

Bu, tüm dosyayı belleğe okur, bu da büyük dosyalar için verimsiz olacağı veya hatta başarısız olacağı anlamına gelir.
üçlü

1
@tripleee Çok büyük dosyaları işlemek için yanıtı güncelledim.
Aaron Hall

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 )

ve biçimlendirilmiş versiyon:

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

4
Her zamanki gibi, Yararsız olduğunu . cat
üçlü

1
@Reishin Bağlantılı sayfa, cather durumda tek bir dosyadan nasıl kaçınabileceğinizi çok daha ayrıntılı olarak açıklıyor . Daha fazla tartışmalı bir Yığın Taşması sorusu var (kabul edilen cevap IMHO kapalı olsa da); stackoverflow.com/questions/11710552/useless-use-of-cat
tripleee

1
Kabuk genellikle bu tür şeylerde zaten çok verimsizdir; Eğer kullanamıyorsanız csplit, bir Awk çözümü muhtemelen bu çözüme göre daha çok tercih edilir ( shellcheck.net vb. tarafından bildirilen sorunları düzeltmiş olsanız bile ; şu anda buradaki tüm hataları bulamadığını unutmayın).
üçlü

@tripleee ama eğer görev bunu awk, csplit vb. olmadan yapmaksa - sadece bash?
2018

1
O zaman cathala işe yaramaz ve senaryonun geri kalanı basitleştirilebilir ve büyük ölçüde düzeltilebilir; ama yine de yavaş olacak. Bkz. Örn. Stackoverflow.com/questions/13762625/…
tripleee

0

Bu, bağlam ayrımı yazdığım türden bir sorundur: 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

Uh, bu aslında standart csplityardımcı programın bir kopyası gibi görünüyor . @ Richard'ın cevabına bakın .
üçlü

Bu aslında en iyi çözüm imo. Bir 98G mysql dökümünü bölmek zorunda kaldım ve bir sebepten dolayı csplit tüm RAM'imi yiyor ve öldürülüyor. Bir seferde yalnızca bir satırla eşleşmesi gerekmesine rağmen. Anlamı yok. Bu python betiği çok daha iyi çalışıyor ve tüm koçu tüketmiyor.
Stefan Midjich

0

İşte her şeyi yapacak bir perl kodu

#!/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);
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.