Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

First attempt at bringing in file for input/output

by catfish1116 (Beadle)
on Oct 19, 2018 at 19:48 UTC ( [id://1224358]=perlquestion: print w/replies, xml ) Need Help??

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

I am taking baby steps to understanding out to read in, modify file(s) and then send modified file to output. Here is my attempt to just read in a file and send it to output.
#!/bin/perl use v5.12; use warnings; my $in = $ARGV[0]; if (! defined $in) { die "Usage: $0 filename"; } my $out = $in; $out =~ s/(\.\w+)?$/out/; if (! open $in_fh, '<', $in ) { die "Can't open '$in': $!"; } if (! open $out_fh, '>', $out) { die "Can't write '$out': $!"; }

Here is the errors that I received:

Global symbol "$in_fh" requires explicit package name at ./exer_9_2 li +ne 14. Global symbol "$out_fh" requires explicit package name at ./exer_9_2 l +ine 1

What 'package' am I missing?

Catfish

Replies are listed 'Best First'.
Re: First attempt at bringing in file for input/output
by stevieb (Canon) on Oct 19, 2018 at 19:59 UTC

    Hey catfish1116,

    That error (thrown by strict, which although not explicitly used, is brought in by the use v5.12; statement) is telling you that you forgot to declare the two file handle variables into a scope (you'd use my in this case here because you want the variable to be lexically scoped to the smallest context possible):

    if (! open my $in_fh, '<', $in ) { # ^^ <- HERE die "Can't open '$in': $!"; }

    Both file opens need to have the my. Nice job on the three-argument open, typically we don't see that with first-timers. The way you write your file open statements is perfectly acceptable (TIMTOWTDI), but in reality, by putting your open in an if() block, you *must* put all of your reads and writes within the same block you've declared the variable. That, or declare the handle variables in a larger (file) scope, with my $in_fh;, then you can proceed to use them throughout the rest of the program without putting the my inside the definition (the if(! open...).

    As an alternative, you can open them up a bit wider by re-writing your code to eliminate the if() condition:

    open my $in_fh, '<', $in or die "can't open '$in': $!";

    When the scope goes out of scope (whether it be a block or entire file), the file handles will automatically close themselves.

    Update: Here are some scoping examples and how they work:

    if block scope:

    if (! open my $fh, '<', $file){ die $!; } # file handle already closed if you try to access here

    Dedicated scope:

    { open my $fh, '<', $file or die $!; # can use the fh here } # but not here, fh auto-closed in the block

    File scope with if:

    my $fh; if (! open $fh, '<', $file){ die $!; } # you can successfully use fh here... # it'll only close if you "close $fh or die $!;", or when the file (pr +ogram) exits # the above example has the same effect of doing the below example, bu +t this is # easier as you don't have a bunch of variables declared at the top of + your file. # the entire idea is to keep variables declared, defined as close to # the place you intend to use them open my $fh, '<', $file or die $!;

    There are varying reasons on why and when you'd expand or limit scope for an open file handle. Sometimes, you need only one line from a file read, so you have a small subroutine (scope/block effectively) that does the work and simply returns a bit of data. Other times, like a logging script say, you need the handle open throughout, and just let perl close it as the program finishes. It's all personal preference, but as with any variable (in any language), it's always best to limit exposure to a variable to a context that is as small, concise and tight as possible. This prevents the "broken from far away by accident" situation.

Re: First attempt at bringing in file for input/output
by talexb (Chancellor) on Oct 19, 2018 at 21:42 UTC

    I'll just jump in and promote the use of autodie, a module that takes care of catching file open errors. For quite a while I wrote file operations in C like this:

    FILE *pif; pif = fopen ( $filename, 'r' ); if ( pif == NULL ) { /* Problem opening file.. */ } .. fclose ( pif );
    When I started writing Perl, I did the same kind of thing ..
    open ( my $in_fh, '<', $filename ) or die "Unable to open $filename for read: $!"; .. close ( $in_fh );
    With autodie, that can be simplified to
    use autodie; open ( my $in_fh, '<', $filename ); .. close ( $in_fh );
    I think that's pretty handy.

    Alex / talexb / Toronto

    Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: First attempt at bringing in file for input/output
by 1nickt (Canon) on Oct 19, 2018 at 22:13 UTC

    I'll also jump in and say that you can go even further than autodie (++talexb) and use a file-handling module for this stuff. It's great that you are learning the basics of opening and closing and printing to files (++), but very quickly that stuff will become boring boilerplate, and the logical computing you do between reading and writing the file will be what you'll want to focus on. Be sure to get well-grounded in the basics, but that includes becoming familiar with the basic toolkit so you can have more fun building things :-)

    I recommend using Path::Tiny for file operations; it keeps things simple and easy and intuitive. For what you are doing in your test script:

    #!/bin/perl use v5.14; use warnings; use Path::Tiny; my $in = $ARGV[0] or die "Usage: $0 <filename>"; path($in)->copy($in =~ s/(\.\w+)?$/.out/r); __END__
    ... for more realistic applications:
    #!/bin/perl use v5.14; use warnings; use Path::Tiny; my $in = $ARGV[0] or die "Usage: $0 <filename>"; my $out = path($in =~ s/(\.\w+)?$/.out/r); for my $line ( path($in)->lines({ chomp => 1 }) { # do something interesting with the line $out->append("$line\n"); } __END__

    Hope this helps!


    The way forward always starts with a minimal test.
Re: First attempt at bringing in file for input/output
by morgon (Priest) on Oct 20, 2018 at 01:02 UTC
    I'll add my two cents on top of what others have posted...

    Firstly I would NOT use autodie until you've learned how to deal with files without it.

    It's a shortcut for people that know what they are doing (and you're not quite there yet).

    Then ALWAYS use strict;. ALWAYS. Absolutely ALWAYS.

    The normal idiom for opening filehandles is this:

    open my $fh, "<", "blah.txt" or die $!;
    This declares a lexical file-handle and opens it, dying if there is an error.

    Feel free to modify it, once you understand what it does.

      As stevieb pointed out, use v5.12; does enable strict.

      Quoting use:

      use VERSION also lexically enables all features... Similarly, if the specified Perl version is greater than or equal to 5.12.0, strictures are enabled lexically as with use strict.

Re: First attempt at bringing in file for input/output
by AnomalousMonk (Archbishop) on Oct 20, 2018 at 00:01 UTC
Re: First attempt at bringing in file for input/output
by Marshall (Canon) on Oct 20, 2018 at 08:48 UTC
    I have some issues with your indenting.
    It is not allowed to optionally create a variable in an if statement.
    I would strongly advise to "use strict;"
    I suggest something like the following...
    #!/usr/bin/perl use warnings; use strict; use File::Basename; die "usage: $0 in_file\n" if @ARGV !=1; my $in_file = shift @ARGV; my($filename, $dir, $suffix) = fileparse($in_file); my $out_file = "$dir/$filename.out"; #change the suffix open my $FH_infile, '<', "$in_file" or die "Can't open $in_file"; open my $FH_outfile, '>', "$out_file" or die "Can't open $out_file";
    I didn't run this code, but I think it is a good general idea...

      Although operator precedence will do the right thing in your code ..

      open my $FH_infile, '<', "$in_file" or die "Can't open $in_file"; open my $FH_outfile, '>', "$out_file" or die "Can't open $out_file";
      I prefer the more explicit
      open ( my $FH_infile, '<', "$in_file" ) or die "Can't open $in_file"; open ( my $FH_outfile, '>', "$out_file" ) or die "Can't open $out_file";
      In addition, I do like to give myself as much information as possible by adding $! to error messages:
      open ( my $FH_infile, '<', "$in_file" ) or die "Can't open $in_file: $!"; open ( my $FH_outfile, '>', "$out_file" ) or die "Can't open $out_file: $!";
      Also, I wasn't aware of File::Basename .. that's pretty handy.

      Alex / talexb / Toronto

      Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

        Don't know why I didn't have $! in the die message. Good catch..including that is a good idea and I do it except when I make a mistake like above!

        While we are talking about die messages, another option that I sometimes use is to suppress the Perl module name and source code line number from the error message. That is done by simply adding a \n to the message, e.g.  die "Can't open $out_file: $!\n"; This choice has to do with who I'm writing the code for. For other programmers, sys admin types, I leave everything in there. Sometimes I write pre-complied .exe's for very inexperienced computer users and in that case, I've found that the extraneous information doesn't help them because they don't understand it and they don't have the source code anyway. I rarely do this, but rare doesn't mean "never".

      It is not allowed to optionally create a variable in an if statement.

      You're probably referring to this piece of documentation (discussed in e.g. Variable Scope):

      NOTE: The behaviour of a my, state, or our modified with a statement modifier conditional or loop construct (for example, my $x if ...) is undefined. The value of the my variable may be undef, any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.

      However, if you are referring to syntax like if (open my $fh, '<', 'file.txt'), this is perfectly legal; from Private Variables via my():

      if ((my $answer = <STDIN>) =~ /^yes$/i) { user_agrees(); } elsif ($answer =~ /^no$/i) { user_disagrees(); } else { chomp $answer; die "'$answer' is neither 'yes' nor 'no'"; }
      the scope of $answer extends from its declaration through the rest of that conditional, including any elsif and else clauses, but not beyond it.

      One very common case of this kind of scoping being while (my $line = <>).

        I liked your post++

        To further clarify this and another thread having to do with while loop and $line=<filehande>:

        #!usr/bin/perl use strict; use warnings; while (my $line =<DATA> and $line !~ /^\s*$/) { print "do somehing with non blank line\n"; } __END__ Value of <HANDLE> construct can be "0"; test with defined() at C:\User +s\mmtho\Documents\PerlProjects\Monks\test_myVar_inwhile.pl line 5. Global symbol "$line" requires explicit package name (did you forget t +o declare "my $line"?) at C:\Users\mmtho\Documents\PerlProjects\Monks +\test_myVar_inwhile.pl line 5. Execution of C:\Users\mmtho\Documents\PerlProjects\Monks\test_myVar_in +while.pl aborted due to compilation errors.
        To fix the first error message,...
        #!usr/bin/perl use strict; use warnings; while (defined (my $line =<DATA>) and $line !~ /^\s*$/) { print "do somehing with non blank line\n"; } __END__ Global symbol "$line" requires explicit package name (did you forget t +o declare "my $line"?) at C:\Users\mmtho\Documents\PerlProjects\Monks +\test_myVar_inwhile_ver2.pl line 6. Execution of C:\Users\mmtho\Documents\PerlProjects\Monks\test_myVar_in +while_ver2.pl aborted due to compilation errors.
        Back up and fix just this error message...
        #!usr/bin/perl use strict; use warnings; my $line; while ($line =<DATA> and $line !~ /^\s*$/) { print "do somehing with non blank line\n"; } __END__ Value of <HANDLE> construct can be "0"; test with defined() at C:\User +s\mmtho\Documents\PerlProjects\Monks\test_myVar_inwhile_ver2.pl line +7. do somehing with non blank line do somehing with non blank line
        To make this work completely:
        #!usr/bin/perl use strict; use warnings; my $line; while (defined($line =<DATA>) and $line !~ /^\s*$/) { print "do somehing with non blank line\n"; } __END__ do somehing with non blank line do somehing with non blank line do somehing with non blank line do somehing with non blank line
      It is not allowed to optionally create a variable in an if statement.

      I cannot see where this is attempted; can you point this out? (I assume you're referring to the OPed code, but I can't see it anywhere in the thread.) (Update: As an aside, note that conditional creation of a variable is "allowed" in the sense that it is not a syntactic error, but it's not a good idea for reasons discussed here and documented here.)

      open my $FH_infile, '<', "$in_file" or die "Can't open $in_file";

      And just to satisfy my idle curiosity, can you say why you use the  "$in_file" idiom (stringizing a string)? (I notice talexb also uses the idiom here.)


      Give a man a fish:  <%-{-{-{-<

        As an aside, note that conditional creation of a variable is "allowed" in the sense that it is not a syntactic error

        Not really a syntax error, no, but:

        $ perl -wMstrict -Mdiagnostics -e 'my $x if 0' Deprecated use of my() in false conditional. This will be a fatal erro +r in Perl 5.30 at -e line 1 (#1) (D deprecated) You used a declaration similar to my $x if 0. Ther +e has been a long-standing bug in Perl that causes a lexical variabl +e not to be cleared at scope exit when its declaration includes a fa +lse conditional. Some people have exploited this bug to achieve a kin +d of static variable. Since we intend to fix this bug, we don't want p +eople relying on this behavior. You can achieve a similar static effect + by declaring the variable in a separate block outside the function, e +g sub f { my $x if 0; return $x++ } becomes { my $x; sub f { return $x++ } } Beginning with perl 5.10.0, you can also use state variables to ha +ve lexicals that are initialized only once (see feature): sub f { state $x; return $x++ } This use of my() in a false conditional has been deprecated since Perl 5.10, and it will become a fatal error in Perl 5.30.

        See also #133543.

Re: First attempt at bringing in file for input/output
by BillKSmith (Monsignor) on Oct 20, 2018 at 03:44 UTC
    You do have to learn how to write I/O, but frequently you can avoid writing it by using Command Switches, especially -I, -n, and -p.
    Bill
Re: First attempt at bringing in file for input/output
by catfish1116 (Beadle) on Oct 29, 2018 at 16:32 UTC
    I've added in 'my' in the 2 if statements.
    #!/bin/perl use v5.12; use warnings; my $in = $ARGV[0]; if (! defined $in) { die "Usage: $0 filename"; } my $out = $in; $out =~ s/(\.\w+)?$/out/; if (! open my $in_fh, '<', $in ) { die "Can't open '$in': $!"; } if (! open my $out_fh, '>', $out) { die "Can't write '$out': $!";

    Now I see this message:

    Usage: ./exer_9_2 filename at ./exer_9_2 line 7.

    I understand from all of the input, (many thanks to unknown friends), that once the if statement is completed then the message handle closes on its' own. I don't want to go a wider scope just yet as I want to get the basics down as far as opening & closing files. What is this current error telling me? Thanks, Catfish

      It is telling you that you did not supply a filename as an argument on the command line when you invoked the script.

      Hi,

      Usage: ./exer_9_2 filename at ./exer_9_2 line 7.
      What is this current error telling me?

      You're obviously serious about learning to program, so you'll want to start more becoming pedantic right away :-)

      That's not an error, that's a message. The error was when you pressed [Enter] after only typing "./exer_9_2".

      The message is being emitted by the die command on line 7. The message tells you that fact, with its last part "at ./exer_9_2 line 7" showing filename and line. Perl is just that awesome.

      The first part of the message is whatever the die command caused to be emitted. In this case, it's the value of the expression Usage: $0 filename. Hopefully you know (since you put it in your code) that $0 resolves to the current filename. So that's why the message names the file twice.

      Knowing all that, you might decide to produce cleaner output, and not die, but just exit instead.

      if (! defined $in) { print "Usage: $0 <filename>\n"; exit(1); }
      (Note the use of the convention of placing an example value for an argument inside angle braces.)

      Hope this helps!


      The way forward always starts with a minimal test.
        ...you might decide to produce cleaner output, and not die, but just exit instead

        And why would you want to do "produce cleaner output" that way? Per the documentation, under normal circumstance, die prints the message to STDERR and exits with a non-zero return value. In other words, it combines your two lines of code into one easier-to-grok line of code, and it prints to STDERR (which makes more sense than printing an error message to STDOUT, which is what your code did), and doesn't require choosing a magic-number exit-code (you chose 1 for unspecified reasons). If you don't like the "at <source_file> line <number>"-part of the error message that die() produces, you can read the documentation's second paragraph and see that all you have to do to prevent that from being appended (ie, "produce cleaner output") is to end your message with a newline. Thus,

        if (! defined $in) { die "Usage: $0 filename\n"; }

        Of course, if the seeker, catfish1116, had followed either your two-line advice, or my one-line alternative, it would have hidden the line number from the error message. Since the seeker was already having difficulty figuring out what had gone wrong, this might have made debug even harder by hiding where the code was dying. So allowing die() to print the line number makes it easier for the programmer to debug; but, contrarily, line numbers in error messages might get in the way for helping the user of the script know what they did wrong. To make the error message more helpful to the user of the script, you could code it as

        if (! defined $in) { die "Usage: $0 <filename>\n\n\tYou must supply a filename when cal +ling this script, otherwise it does not know what file to read\n"; }

        In the end, the programmer needs to decide which is better: making the error message more clear to a future programmer, or making it more clear to the end user. (I'd probably recommend the latter; besides, if error messages are unique, then the programmer can search the code for the uniqueness of the message, even if line numbers aren't presented. Or, as a compromise, include the extra portion in my second example, but don't end it with the "\n", so that way it is more explanatory to the user, but still gives the line number to the programmer.)

A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

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

    No recent polls found