Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have a file ABC_TabDelim.txt that contains the following:

00:00:00:00 00:00:05:00 01SC_001.jpg
00:00:14:29 00:00:19:29 01SC_002.jpg
00:01:07:20 00:01:12:20 01SC_003.jpg
00:00:00:00 00:00:03:25 02MI_001.jpg
00:00:03:25 00:00:08:25 02MI_002.jpg
00:00:35:27 00:00:40:27 02MI_003.jpg
00:00:00:00 00:00:05:00 03Bi_001.jpg
00:00:05:19 00:00:10:19 03Bi_002.jpg
00:01:11:17 00:01:16:17 03Bi_003.jpg
00:00:00:00 00:00:05:00 04CG_001.jpg
00:00:11:03 00:00:16:03 04CG_002.jpg
00:01:12:25 00:01:17:25 04CG_003.jpg

I would like to split this into multiple files for each instance of 00:00:00:00, outputting it as ABC01_TabDelim.txt, ABC02_TabDelim.txt, ABC03_TabDelim.txt, etc.

So 00:00:00:00 would indicate a new file should begin. Is there any way I can accomplish this with a Perl script?

share|improve this question

5 Answers 5

This will work for the given format. This assumes the file will always start with 00:00:00:00.

#!/usr/bin/env perl

use strict;
use warnings;

open(my $infh, '<', 'ABC_TabDelim.txt') or die $!;

my $outfh;
my $filecount = 0;
while ( my $line = <$infh> ) {
    if ( $line =~ /^00:00:00:00/ ) {
        close($outfh) if $outfh;
        open($outfh, '>', sprintf('ABC%02d_TabDelim.txt', ++$filecount)) or die $!;        
    }
    print {$outfh} $line or die "Failed to write to file: $!";
}

close($outfh);
close($infh);
share|improve this answer
    
Don't use a regexp if you don't have to, use if (substr($line, 0, 11) eq "00:00:00:00"). –  goldilocks Feb 13 '13 at 16:20
    
@goldilocks: what? The regex expresses the intent just fine and avoids having to count the chars (and forgetting to update something if the pattern ever changes). –  Mat Feb 13 '13 at 16:43
    
@Mat Too bad we lack a string.startswith() method that solves that problem :). –  jordanm Feb 13 '13 at 16:46
    
@Mat: the method here is a very minor difference WRT to the maintenance ("forgetting to update...") of hardcoded values (but point taken). It's not a big deal, just I would consider it poor programming because a regexp is less efficient, resource wise, and using them for everything is kinda over-the-top lazy. No offence ;) Also, using substr() is a more specific statement about what you are doing (exact match). –  goldilocks Feb 13 '13 at 16:58

Here you go. No error checking, run as, e.g., perl split file-to-munge

Update: Script cleanup as suggested by goldilocks

#!/usr/bin/perl

$n = 1;
while(<>) {
    if(/^00:00:00:00/) {
        close($out) if(n != 1);
        $fn = sprintf("ABC%02d_TabDelim.txt", $n++);
        open($out, ">", "$fn");
    }
    print OUT;
}
share|improve this answer
    
-1 for use of FILE glob and 2 arg open! Sorry. –  goldilocks Feb 13 '13 at 16:24
    
@goldilocks, that is quite idiomatic Perl 5... –  vonbrand Feb 13 '13 at 16:25
3  
It's actually idiomatic of Perl <5.6 (c.2000), when it was the only choice (see here: perldoc.perl.org/…) There are lots of explanations around for why this is a bad habit, (eg: securecoding.cert.org/confluence/pages/…), not the least of which is those bareword typeglob handles are by default in the global namespace regardless of where you declare them. socialtext.net/perl5/bareword_uppercase_filehandles –  goldilocks Feb 13 '13 at 16:37
    
@goldilocks, and that is relevant for a throwaway script because...? –  vonbrand Feb 13 '13 at 16:42
2  
I am not downvoting but demonstrating bad practices to a new user is not a good thing. For your own throwaway, it's fine. –  jordanm Feb 13 '13 at 16:48

If the output for that sample input is expected to be 4 files, each with 3 lines, each 1st line being one starting with “00:00:00:00” and the other 2 lines the following ones, this will do it:

perl -ne 'if(/^[0:]{11}/){close F if$f;open F,sprintf(">ABC%02d_TabDelim.txt",++$f)}print F' ABC_TabDelim.txt
share|improve this answer

You don't need Perl for that: you can use the standard shell utility csplit. There's one limitation: the name of the output files has to be xxx00, xxx01, etc., so you'll need to add the desired suffix afterwards.

csplit -s -f ABC ABC_TabDelim.txt '/^00:00:00:00/' '{999999999}'
rm ABC00  # empty file (containing everything up to the first 00:00:00:00)
for x in ABC[0-9][0-9]; do mv "$x" "${x}_TabDelim.txt"; done

The {*} argument to repeat the previous delimiter as many times as possible is a GNU extension. POSIX csplit requires a first pass with grep.

share|improve this answer

You have a solution for perl, here's one way you could do it with awk:

awk '/00:00:00:00/ { out = sprintf("ABC%02d_TabDelimit.txt", ++i) } { print > out }' ABC_TabDelim.txt

If you have to split into many files you would want to close each file as you go along, prepend the sprintf function with if(out) close(out):

awk '/00:00:00:00/ { if(out) close(out); out = sprintf("ABC%02d_TabDelimit.txt", ++i) } { print > out }' ABC_TabDelim.txt
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.