Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Use 2 files and print each line of each one side-by-side

by Anonymous Monk
on Feb 18, 2014 at 17:49 UTC ( [id://1075362]=perlquestion: print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,
I have 2 files and I want to open them and print the first line of the first line, then <tab> and then the first line of the second file, and so on...
I tried the following:
print "Please specify 2 filenames\n"; print "Filename 1:"; my $userfile1=<STDIN>; chomp($userfile1); print "Filename 2:"; my $userfile2=<STDIN>; chomp($userfile2); open OUT, ">CONCATENATED_FILES"; open (USER1, "<$userfile1") or die "Couldn't open file: $userfile1"; open (USER2, "<$userfile2") or die "Couldn't open file: $userfile2"; while(<USER1>) { $line1=$_; chomp $line1; print OUT $line1."\t"; while(<USER2>) { $line2=$_; chomp $line2; print OUT $line2."\n"; } } close USER1; close USER2; close OUT;

but without success...What am I doing wrong?

Replies are listed 'Best First'.
Re: Use 2 files and print each line of each one side-by-side
by roboticus (Chancellor) on Feb 18, 2014 at 18:01 UTC

    The first thing you're doing wrong is using two loops. You definitely want a loop, but you want to read one line from each file in each pass of the loop. So get rid of the inner loop.

    Next, you'll want to figure out when to end your loop. I'll assume that you want to end the loop only after you've completed both files.

    So I'd suggest doing it more like this:

    while (!eof(USER1) and !eof(USER2)) { my $line1 = <USER1>; my $line2 = <USER2>; . . . }

    The condition in the while stops only when both files are finished. (See perldoc -f eof)

    Update: s/feof/eof/g, thanks to Kenosis, who generously thought I meant eof, when it was just me mistaking C for perl. (In my defense, they have a similar spelling. Oh, wait, who do I think I'm fooling with that?)

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: Use 2 files and print each line of each one side-by-side (One-liner)
by BrowserUk (Patriarch) on Feb 18, 2014 at 19:24 UTC

    A simple one-liner will do for this:

    C:\test>type a.txt A00001 A00002 A00003 A00004 A00005 A00006 A00007 A00008 A00009 A00010 C:\test>type b.txt B00001 B00002 B00003 B00004 B00005 B00006 B00007 B00008 B00009 B00010 C:\test>perl -nle"printf qq[%10s\t%10s], $_, scalar <STDIN>" a.txt <b. +txt A00001 B00001 A00002 B00002 A00003 B00003 A00004 B00004 A00005 B00005 A00006 B00006 A00007 B00007 A00008 B00008 A00009 B00009 A00010 B00010

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      For relative values of simple...
        For relative values of simple...

        Huh? Relative to what? A plank.

        It's two well-known switches and a print statement.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Use 2 files and print each line of each one side-by-side
by davido (Cardinal) on Feb 18, 2014 at 19:00 UTC

    If the files are of a size where slurping isn't prohibitively expensive, you could do this:

    use IO::Prompt::Tiny 'prompt'; use List::Util 'max'; use File::Slurp 'read_file'; my @files = map { my $fn = $ARGV[$_] // prompt 'Please enter filename #' . ( $_+1 ) +. ':'; read_file $fn, array_ref => 1, chomp => 1; } 0 .. 1; for( 0 .. max map { scalar @{$_} } @files ){ my $left_line = $files[0][$_] // ''; my $right_line = $files[1][$_] // ''; print "$_: $left_line\t$right_line\n"; }

    Otherwise, you need to iterate over each, and detect when both file handles have been exhausted. But you must do it in a single pass. Your existing script has nested loops so that for each line in USER1, you attempt to read all of USER2.

    If we can assume that user input comes from the command line, we can skip the prompting:


    Dave

Re: Use 2 files and print each line of each one side-by-side
by Kenosis (Priest) on Feb 18, 2014 at 19:05 UTC

    Always, and without fail, include the following at the top of your Perl scripts:

    use strict; use warnings;

    These pragmas will likely save you many headaches by showing you problematic areas in your script.

    Use the three-argument form of open. For example:

    open $USER1, '<', $userfile1 or die "Couldn't open file: $userfile1 - +$!"; ... while(<$USER1>) { ...

    Note also the use of $!. It's good that you're handling errors; it's better if you're shown exactly what produced an exception if there's an error, and $! will let you know.

    You need to do the same when opening a file for writing. However, you can do the just following when opening files:

    open $OUT, '>', "CONCATENATED_FILES"; ... open $USER1, '<', $userfile1;

    if you include the following pragma:

    use autodie;

    Looping through both files is a good way to achieve your desired results, and roboticus provided a solution for handling reading from both files. Since, however, your running this script from the command line, here's another option--in case you may be interested:

    use strict; use warnings; use File::Slurp qw/read_file/; use List::MoreUtils qw/zip/; chomp( my @file1 = read_file $ARGV[0] ); chomp( my @file2 = read_file $ARGV[1] ); my @combined = zip @file1, @file2; for my $i ( 0 .. $#file1 ) { last if !defined $file1[$i] or !defined $file2[$i]; print "$file1[$i]\t$file2[$i]\n"; }

    Usage: perl script.pl file1 file2 >combinedFile

    The script uses File::Slurp to read each file's contents into an array. Next, is uses List::MoreUtils to 'zip' or interleave the elements of the two arrays. When iterating through the arrays for printing, the script checks that both elements are defined in case one file has more lines than the other.

    The above script will work just fine with small or not-too-large files, since their entire contents are read into arrays. If, however, your files are large, stick with just iterating through each file, a line at a time.

    Hope this helps!

Re: Use 2 files and print each line of each one side-by-side
by Laurent_R (Canon) on Feb 18, 2014 at 23:26 UTC
    Just for the fun, a HOP version using closures and function factories. First a long explanatory version:
    use strict; use warnings; my $file1 = make_func (shift); my $file2 = make_func (shift); while (1) { my $line1 = $file1->(); my $line2 = $file2->(); last unless defined $line1 and defined $line2; print "$line1 $line2 \n"; } sub make_func { my $file = shift; open my $FH, "<", $file or die "could not open $file $!"; return sub {my $c = <$FH>; return unless defined $c; chomp $c; return $c } }
    And now a more concise form:
    use strict; use warnings; my $file1 = make_func (shift); my $file2 = make_func (shift); my ($line1, $line2); chomp $line1 and print $line1, $line2 while ($line1 = $file1->() and $ +line2 = $file2->()); sub make_func { my $file = shift; open my $FH, "<", $file or die "could not open $file $!"; sub {<$FH>; } }
    It can probably be made more concise, but I have no time right now.

      Also for fun, a Perl 6 version.
      Note that this only works for files of the same length, because Z terminates (without error) when the shorter list ends.

      use v6; sub MAIN ( $in_path1, $in_path2, $out_path = 'CONCATENATED_FILES' ) { my $in1 = open $in_path1, :r; my $in2 = open $in_path2, :r; my $out = open $out_path, :w; for $in1.lines Z $in2.lines -> $line1, $line2 { $out.say: "$line1\t$line2"; } .close for $out, $in1, $in2; }
Re: Use 2 files and print each line of each one side-by-side
by hippo (Bishop) on Feb 18, 2014 at 23:07 UTC

    Are you aware of the paste command? No need to go reinventing the wheel on this one.

      paste is a nice solution. However, the OP didn't state whether the files had the same number of lines or not, but did mention printing from both files. In the case of files with a different number of lines, paste continues printing up to the end of the larger file--and corresponding blanks for the smaller--after the smaller file's exhausted. This exception does not meet the OP's specs of printing from both files on each line. However, it may be the case the the files have the same number of lines; if so, this simply isn't an issue.

Re: Use 2 files and print each line of each one side-by-side
by kcott (Archbishop) on Feb 19, 2014 at 20:06 UTC

    Here's another way to do it using Tie::File and List::MoreUtils::pairwise().

    Here's the input data:

    $ cat pm_1075362_1.txt file 1 line 1 file 1 line 2 $ cat pm_1075362_2.txt file 2 line 1 file 2 line 2 file 2 line 3 $ cat pm_1075362_3.txt file 3 line 1 file 3 line 2

    Here's the script showing examples for: a) first file shorter than the second; b) first file longer than the second; c) both files with same length.

    #!/usr/bin/env perl -l use strict; use warnings; use autodie; use Tie::File; use List::MoreUtils qw{pairwise}; my ($file1, $file2, $file3) = map { "pm_1075362_$_.txt" } 1 .. 3; tie my @file1, 'Tie::File', $file1; tie my @file2, 'Tie::File', $file2; tie my @file3, 'Tie::File', $file3; print '*** File 1 (2 lines) and File 2 (3 lines):'; print_tabbed_pairs(\@file1, \@file2); print '*** File 2 (3 lines) and File 1 (2 lines):'; print_tabbed_pairs(\@file2, \@file1); print '*** File 1 (2 lines) and File 3 (2 lines):'; print_tabbed_pairs(\@file1, \@file3); sub print_tabbed_pairs { my ($file1, $file2) = @_; print for pairwise { join "\t" => (defined $a ? $a : ''), (defined $b ? $b : '') } @$file1, @$file2; }

    Output:

    *** File 1 (2 lines) and File 2 (3 lines): file 1 line 1 file 2 line 1 file 1 line 2 file 2 line 2 file 2 line 3 *** File 2 (3 lines) and File 1 (2 lines): file 2 line 1 file 1 line 1 file 2 line 2 file 1 line 2 file 2 line 3 *** File 1 (2 lines) and File 3 (2 lines): file 1 line 1 file 3 line 1 file 1 line 2 file 3 line 2

    -- Ken

Re: Use 2 files and print each line of each one side-by-side
by Discipulus (Canon) on Feb 19, 2014 at 08:10 UTC
    Among other options, given by wiser monks, you can consider my Multiplexing log output: Log4perl of the poors that is more convoluted then your needs..

    i have misunderstood the OP..
    hth
    L*
    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1075362]
Approved by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (5)
As of 2024-04-18 19:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found