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

Dear Monks - I am novice asking for help or advice please;
To modify a text file by adding one extra line spacing between each line, I initially hardcoded the name of target file... 3.txt into my code.

How to revise so that ANY file can be used (ie. perl myprogram.pl anyfile.txt) from the command line?
I have started to read about IO::Handles ... would you recommended that or please say what would be a better method for this problem?


open(PAGE,"3.txt") || die "I can't open yourfile.txt"; @File =<PAGE> ; #store the contents of the file into array close(PAGE); $Ctr = 0; foreach (@File){ # loop through file { $_ = $_ . "\n" #add extra newline to each line } $Ctr = $Ctr+1; #increment through the array } #end foreach open(PAGE,">3.txt") || die "I can't open yourfile.txt"; #write the a +rray contents print PAGE @File; close(PAGE);

Replies are listed 'Best First'.
Re: IO::Handles ... any good?
by toolic (Bishop) on Mar 22, 2009 at 18:30 UTC
    I have never used IO::Handle (without the "s"), but shift will remove the 1st argument on the command line for you:
    my $filename = shift; open(PAGE, $filename) || die "I can't open yourfile.txt";

    $filename would become "anyfile.txt" in your example command line.

    Update: There are other ways of modifying a file in-place. A Super Search where title contains all of "edit", "file", "in", "place" show some ?node_id=3989;HIT=edit%20file%20in%20place;re=N

    If your input file is large, these other methods would save memory by not storing the entire contents at once.

      I've been reading all afternoon your advice was perfect - thanks so much.

Re: IO::Handles ... any good?
by gulden (Monk) on Mar 22, 2009 at 18:38 UTC
    The easy way:
    my $file = $ARGV[0] || die "Synopsys: $0 <filename>" ; print "This is the filename to be processed: $file\n";

      The  || operator has higher precedence than the  = operator so you should either enclose the assignment in parentheses:

      ( my $file = $ARGV[0] ) || die "Synopsys: $0 <filename>" ;

      or use the lower precedence  or operator:

      my $file = $ARGV[0] or die "Synopsys: $0 <filename>" ;

      Also, that test will fail if you have a file named  '0'.

        so you should either enclose the assignment in parentheses:

        Why? The intention is to check $ARGV[0], so || is perfectly acceptable.

        There's is a problem with both your solution and your parent's: It won't work for a file named "0". The following will, however:

        my ($file) = @ARGV or die(...);
Re: IO::Handles ... any good?
by linuxer (Curate) on Mar 22, 2009 at 18:40 UTC

    You should use strict and warnings in your script. They help you in writing better code.

    You can use the predefined array @ARGV. It contains all arguments given to the script.

    With ./script.pl file1.txt file2.txt you should find the two file names inside @ARGV.

    Then you can do your modifications foreach file...

    #! /usr/bin/perl use strict; use warnings; FILE: for my $file ( @ARGV ) { # check if $file contains a file name if ( !-f $file ) { warn "Sorry, '$file' doesn't look like a file!\n"; next FILE; } # if we can't open the file for reading, warn user if ( !open(my $infh, '<', $file) ) { warn "Sorry, couldn't open $file: $!\n"; } # we could open the file for reading else { # so, let's try to open a temporary file for writing if ( !open( my $outfh, '>', $file.'.new' ) ) { # and warn user if it failed warn "Sorry, couldn't open $file.new for writing: $!\n"; } else { # we now have two filehandles: # one to read from and one to write to. # read linewise from input filehandle while ( my $line = <$infh> ) { # print (to output filehandle) original line and append a newl +ine print $outfh $line, "\n" or die "writing to $file.new failed: +$!\n"; } # writing should be done now; close filehandle close $outfh or die "closing $file.new failed: $!\n"; } # reading should be done now; close filehandle close $infh or die "closing $file failed: $!\n"; } }

    code was hacked quickly and is not tested!

    You can do this even in one line ;o)

    perl -i.bak -pe '$_.="\n"' file1.txt file2.txt file...

    this was quickly tested and worked so far ;o)

    Updates:

    • s/$/\n/ replaced with $_.="\n"

      Quite a bit beyond me but... wow, I will use as a shell alias - thanks.
      Here's my effort at decoding your line :

      perl <br> -i #edit in place .bak #but make a new file -p # read every line from default arg e # excecute code from command line ' #code for the default while loop $_ # default variable, each line of file . # and = # equal to " #start printing this literally \n #new line " #finish literal printing ' # end/start next loop
        perl -i # edit in place .bak # rename original file by appending .bak to filename; print to n +ew file with original name -p # use a while loop to read each file; print each line (is stored + in $_) which you have read (and modified) e # excecute code from command line ' # start of code to be executed (for the default while loop) $_ # default variable, each line of file .= # append the following to yourself ($_) and assign it to you "\n" # string to be appended ' # end of code

        $_ .= "\n"; is the shorter form of $_ = $_ . "\n";

        Check perlrun and look for the explanation for option -i. They have some examples there. -p should be explained in more details, too.

      The single command line is quite interesting. However, it's possible to run that command without changing the original file?

        Currently I only have this solution for a single file:

        perl -pe '$_.="\n"' file1.txt > file1.new

        It's untested but should read file1.txt and print the modified version into file1.new.

        I have no idea, how to perform this with multiple files in one (simple) command.

        You could still use a shell for loop to perform this upon several files...

        # bash for FILE in *.txt; do perl -pe '$_.="\n"' $FILE > $FILE.new; done