Eu tenho um arquivo com -|
como delimitador após cada seção ... preciso criar arquivos separados para cada seção usando unix.
exemplo de arquivo de entrada
wertretr
ewretrtret
1212132323
000232
-|
ereteertetet
232434234
erewesdfsfsfs
0234342343
-|
jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|
Resultado esperado no arquivo 1
wertretr
ewretrtret
1212132323
000232
-|
Resultado esperado no arquivo 2
ereteertetet
232434234
erewesdfsfsfs
0234342343
-|
Resultado esperado no arquivo 3
jdhg3875jdfsgfd
sjdhfdbfjds
347674657435
-|
Respostas:
Uma linha, sem programação. (exceto o regexp etc.)
csplit --digits=2 --quiet --prefix=outfile infile "/-|/+1" "{*}"
testado em:
csplit (GNU coreutils) 8.30
Notas sobre o uso no Apple Mac
"Para usuários do OS X, observe que a versão
csplit
que vem com o sistema operacional não funciona. Você vai querer a versão em coreutils (instalável via Homebrew), que é chamadogcsplit
." - @Danial"Só para adicionar, você pode fazer com que a versão do OS X funcione (pelo menos com High Sierra). Você só precisa ajustar um pouco as args
csplit -k -f=outfile infile "/-\|/+1" "{3}"
. Os recursos que parecem não funcionar são"{*}"
, eu tive que ser específico o número de separadores e necessário adicionar-k
para evitar a exclusão de todos os arquivos de saída se não conseguir encontrar um separador final. Além disso, se você quiser--digits
, será necessário usar-n
. " - @Pebblfonte
--elide-empty-files
, caso contrário, haverá um arquivo vazio no final.--digits=2
controla o número de dígitos usados para numerar os arquivos de saída (2 é o padrão para mim, então não é necessário).--quiet
suprime a saída (também não é realmente necessário ou solicitado aqui).--prefix
especifica o prefixo dos arquivos de saída (o padrão é xx). Assim, você pode pular todos os parâmetros e obter arquivos de saída comoxx12
.csplit -k -f=outfile infile "/-\|/+1" "{3}"
. Recursos que parecem não funcionar são o"{*}"
, eu tive que ser específico sobre o número de separadores, e precisava adicionar-k
para evitar a exclusão de todos os outfiles se não conseguir encontrar um separador final. Além disso, se quiser--digits
, você precisa usar-n
.awk '{f="file" NR; print $0 " -|"> f}' RS='-\\|' input-file
Explicação (editado):
RS
é o separador de registro e esta solução usa uma extensão gnu awk que permite ter mais de um caractere.NR
é o número do registro.A instrução print imprime um registro seguido por
" -|"
em um arquivo que contém o número do registro em seu nome.fonte
RS
é o separador de registro e esta solução usa uma extensão gnu awk que permite ter mais de um caractere. NR é o número do registro. A instrução print imprime um registro seguido por "- |" em um arquivo que contém o número do registro em seu nome.>
, então você pode construí-lo como quiser. por exemplo,print $0 "-|" > "file" NR ".txt"
awk '{f="file" NR; print $0 " -|" > f}'
O Debian tem
csplit
, mas não sei se isso é comum a todas / à maioria / outras distribuições. Se não, porém, não deve ser muito difícil rastrear o código-fonte e compilá-lo ...fonte
csplit
está no POSIX, eu esperaria que estivesse disponível essencialmente em todos os sistemas do tipo Unix.csplit --prefix gold-data - "/^==*$/
vscsplit --prefix gold-data - "/^=+$/
. Pelo menos GNU grep tem-e
.Resolvi um problema um pouco diferente, onde o arquivo contém uma linha com o nome onde o texto a seguir deve ir. Este código perl faz o truque para mim:
#!/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 "$_"}; }
fonte
while
laço final . Se encontrar omff
regex no início da linha, ele usará o resto da linha como o nome do arquivo para abrir e começar a escrever. Ele nunca fecha nada, então ficará sem identificadores de arquivo após algumas dezenas.while
loop final e mudando parawhile (<>)
O seguinte comando funciona para mim. Espero que ajude.
awk 'BEGIN{file = 0; filename = "output_" file ".txt"} /-|/ {getline; file ++; filename = "output_" file ".txt"} {print $0 > filename}' input
fonte
close
o arquivo antigo ao iniciar um novo.if (file) close(filename);
antes de atribuir um novofilename
valor.; close(filename)
. Muito simples, mas realmente corrige o exemplo acimaVocê também pode usar o awk. Não estou muito familiarizado com o awk, mas as opções a seguir parecem funcionar para mim. Ele gerou part1.txt, part2.txt, part3.txt e part4.txt. Observe que o último arquivo partn.txt gerado está vazio. Não sei como consertar isso, mas tenho certeza de que poderia ser feito com um pequeno ajuste. Alguma sugestão de alguém?
arquivo awk_pattern:
BEGIN{ fn = "part1.txt"; n = 1 } { print > fn if (substr($0,1,2) == "-|") { close (fn) n++ fn = "part" n ".txt" } }
comando bash:
awk -f awk_pattern input.file
fonte
Aqui está um script Python 3 que divide um arquivo em vários arquivos com base em um nome de arquivo fornecido pelos delimitadores. Arquivo de entrada de exemplo:
# Ignored ######## FILTER BEGIN foo.conf This goes in foo.conf. ######## FILTER END # Ignored ######## FILTER BEGIN bar.conf This goes in bar.conf. ######## FILTER END
Aqui está o script:
#!/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)
Finalmente, aqui está como você o executa:
$ python3 script.py -i input-file.txt -o ./output-folder/
fonte
Use
csplit
se você tiver.Se você não tem, mas tem Python ... não use Perl.
Leitura lenta do arquivo
Seu arquivo pode ser muito grande para ser guardado na memória de uma vez - ler linha por linha pode ser preferível. Suponha que o arquivo de entrada seja denominado "samplein":
$ 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"
fonte
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 )
e a versão formatada:
#!/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; )
fonte
cat
é inútil .cat
em um único arquivo em todas as situações. Há uma pergunta sobre Stack Overflow com mais discussão (embora a resposta aceita seja IMHO desativado); stackoverflow.com/questions/11710552/useless-use-of-catcsplit
, uma solução Awk é provavelmente muito preferível a esta solução (mesmo se você fosse corrigir os problemas relatados por shellcheck.net etc; note que atualmente ele não encontra todos os bugs nele).cat
ainda é inútil, e o resto do script pode ser bastante simplificado e corrigido; mas ainda será lento. Veja, por exemplo, stackoverflow.com/questions/13762625/…Este é o tipo de problema para o qual escrevi divisão de contexto: 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
fonte
csplit
utilitário padrão . Veja a resposta de @richard .Aqui está um código perl que fará a coisa
#!/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);
fonte