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

I know there is a simple answer to this, but I can't seem to find it. I'm self taught so don't burn me too bad. Basically, I need to pass a variable from one sub to another. Here's a simpler sample of what I am trying to do (the actual is large).
#!/usr/bin/perl -wT use strict; use CGI qw/:standard/; $ENV{PATH} = "/cgi-bin"; # untaint and return value sub IP_check { my $_info2 = $ENV{'REMOTE_ADDR'}; my $info; if (!$_info2) { $info2 = "No IP address available"; return $info2; }elsif ($_info2 !~ /[\%]+/ && $_info2 =~ /^(\d{1,3}\.{1})(\d{1,3}\.{1} +)(\d{1,3}\.{1})(\d{1,3})$/) { # my $info2 = "$1$2$3$4"; sorry, forgot to delete the "my" # from the original posting $info2 = "$1$2$3$4"; return $info2; }else { # my info2 = "Bad IP address submitted"; same "my" problem #here $info2 = "Bad IP address submitted"; return $info2; } } if (param()) { # do other stuff here before if statement if (IP_check()) { # do some stuff here before calling sub &mailoff; exit;} } # html form stuff is located here sub mailoff { # open mail handler stuff - omitted for simplicity print MAIL "here is the ip address $info2"; # now the closing stuff }

Can someone point me in the right direction?

Thanks,
Dave

Replies are listed 'Best First'.
Re: passing variables between subroutines
by g0n (Priest) on Jan 16, 2006 at 15:45 UTC
    If your variable is a simple scalar, you can pass it to the sub as follows:

    my $return = mailoff($myvar); sub mailoff { my $var = $_[0]; #do stuff return $vartoreturn }

    If you want to pass arrays and hashes around, you can pass them as a list:

    mailoff(@array); sub mailoff { my @array = @_; } .. mailoff(%hash) sub mailoff { my %hash = @_; }
    But a) you can only pass one array or hash that way, and b) it's a copy, not the original, so changes won't be seen in your main program (that's true in the first example as well, hence the return variable).

    Alternatively, you could look at perlref, and pass references around. There's some simple introduction to that technique in Why use references?.

    --------------------------------------------------------------

    "If there is such a phenomenon as absolute evil, it consists in treating another human being as a thing."

    John Brunner, "The Shockwave Rider".

      It's best to avoid passing non-scalars around that way. I think you're generally better served by passing the references
      my @test = (1,2); my @test2 = (3,4); foo(\@test,\@test2); sub foo { my ($array,$array2) = @_; print $_ ."\n" for(@{$array}); print $_ ."\n" for(@{$array2}); }
        It's best to avoid passing non-scalars around that way

        I have to say I disagree with that as stated. Certainly, there are many cases where passing a reference is preferable, but it depends on the situation. My default is to pass a simple list and switch to passing references when there's a clear need. I'd even go so far as to say it's best to err on the side of not making references when they're not needed.

        It's best to avoid passing non-scalars around that way. I think you're generally better served by passing the references

        First of all the person you're replying to already pointed out that possibility in his post. Said this, I beg to differ with your claim and to stress that it is actually a possibility, albeit one that -as he also pointed out- turns into a necessity under certain circumstances. But generally speaking there are situations in which passing a reference is preferrable and others in which it is preferrable not to. However, stating a claim like that may easily turn into cargo culting the use of referencing and dereferencing all the time, which has already been observed around.

Re: passing variables between subroutines
by Tanktalus (Canon) on Jan 16, 2006 at 15:51 UTC

    Two things. First, I'm not sure why you're validating the remote address. In this chunk of code, there are a number of minor issues, and one larger issue. We'll start with a minor issue: using $ENV{REMOTE_ADDRESS} instead of CGI::remote_host(). It returns what you want, and, should some future CGI standard change how this gets passed around (say under a future mod_perl), you won't need to change anything to work with it - you'll just need to upgrade CGI.pm.

    The major issue is the return keyword you're using. I'm not sure what you are attempting to do here. The return keyword is only useful inside a sub, and this doesn't seem to be inside a sub.

    To answer your question, change &mailoff to mailoff($info2) (if that's the variable you're trying to pass). And then inside your mailoff sub, do this:

    sub mailoff { my $ip_address = shift; #open mail handler stuff print MAIL "here is the ip address $ip_address"; #close }
    Note that the variable name we use inside mailoff is completely unrelated to the variable name we use to call mailoff with. They can be the same name, but I changed it to show that they commonly are different.

      Tanktalus, Thanks for your reply. I do appreciate the help. I am very green to perl and "perl speak". The reason I am validating an environment variable is the fact that the value could be passed through the url (if someone happened to guess the variable name). From everything I read, it is suggested that "security through obscurity" is wrong. I just want to make sure that my script will be as "bullet proof" as possible. As for the CGI::remote_host(), I'll have to look into that.

      My script was basically pieced together from other scripts I had found. I tried the best I could to understand what was going on and thus the reason for the incorrect use of "return".

      I think I understand what you are saying, and I will give this a try.

        Sorry, I completely misread your original code. I see what the return is doing now. It's inside another sub. I just didn't see the sub because of your indenting. While it's true that whitespace (mostly) doesn't matter to the computer, the fact is that code is not written for the computer as much as it is written for the next human that has to read it (usually yourself). You should check out perlstyle for some ideas on how to improve the layout of your code. For example, your IP_check sub could look better this way:

        sub IP_check { my $_info2 = $ENV{'REMOTE_ADDR'}; my $info; if (!$_info2) { $info2 = "No IP address available"; return $info2; } elsif ($_info2 !~ /[\%]+/ && $_info2 =~ /^(\d{1,3}\.{1})(\d{1,3}\.{1})(\d{1,3}\.{1})(\ +d{1,3})$/) { my $info2 = "$1$2$3$4"; return $info2; } else { my $info2 = "Bad IP address submitted"; return $info2; } }
        Notice how I did nothing but add whitespace. It's much easier to follow now. Now that I can read it properly, the return is perfectly legal. It may not be completely optimal, but it's legal.

        Instead, if I can just focus on this sub for a minute, you may want to use the $info you declared rather than the global $info2 you are using. That helps keep the subroutine from having side effects - side effects aren't necessarily bad, but when you can avoid them, do so, as it will make your code easier for the human to follow.

        Or, to make the subroutine just a bit tighter, you don't need that at all. For example:

        sub IP_check { my $_info2 = $ENV{'REMOTE_ADDR'}; if (!$_info2) { return "No IP address available"; } elsif ($_info2 !~ /[\%]+/ && $_info2 =~ /^(\d{1,3}\.{1})(\d{1,3}\.{1})(\d{1,3}\.{1})(\ +d{1,3})$/) { return "$1$2$3$4"; } else { return "Bad IP address submitted"; } }
        Now, some people get mad at having multiple returns in a single subroutine, arguing that it's easier to follow when there's a single entry and a single exit to each function. I'm not a complete believer in this style, but I'm not going to say it's wrong, either. So here's that example:
        sub IP_check { my $_info2 = $ENV{'REMOTE_ADDR'}; my $ip; if (!$_info2) { $ip = "No IP address available"; } elsif ($_info2 !~ /[\%]+/ && $_info2 =~ /^(\d{1,3}\.{1})(\d{1,3}\.{1})(\d{1,3}\.{1})(\ +d{1,3})$/) { $ip = "$1$2$3$4"; } else { $ip = "Bad IP address submitted"; } return $ip; }
        I'll leave it up to you to decide which you think is more readable and easier to understand. Then there's the more perlish way to do all of the above - but it's a wee bit tricky, I'd suggest one of the above over this next example. This next one only works because the if statement is the last statement in the subroutine since, in perl, the return value from a sub is determined from the last value in the subroutine if no 'return' is otherwise encountered. (It's because it requires a long explanation that I don't recommend it ;-})
        sub IP_check { my $_info2 = $ENV{'REMOTE_ADDR'}; my $ip; if (!$_info2) { "No IP address available"; } elsif ($_info2 !~ /[\%]+/ && $_info2 =~ /^(\d{1,3}\.{1})(\d{1,3}\.{1})(\d{1,3}\.{1})(\ +d{1,3})$/) { "$1$2$3$4"; } else { "Bad IP address submitted"; } }
        Now the next question is ... how to use it? That's easy - regardless of which one of the above you use, you call it like this: my $info2 = IP_check(); Hope that helps!

Re: passing variables between subroutines
by xdg (Monsignor) on Jan 16, 2006 at 15:53 UTC
    &mailoff;

    This syntax calls the named sub using the same @_ array as currently exists -- which doesn't since you aren't in a subroutine. Is that what you intended or are you just using the & calling style? (It is an older style held over from Perl 4 and generally not necessary unless you actually want to reuse @_.)

    From reading the way your code is structured, it doesn't look like you're familiar with subroutine parameters. I think you might want to read perlsub, which talks about how subroutines pass arguments.

    Here's some typical ways of passing and returning values:

    sub some_function { my $arg1 = shift; # shift the first value off of @_ my ($arg2, $arg3, ) = @_; # or copy all of @_ at once # process stuff return $some_value; } # called like this $value = some_function( 1, 2, 3 );

    For your problem, you probably want to store the result of IP_check and pass it to mailoff like this: mailoff( $ip_check_result ). mailoff will then need to shift the argument off of @_ as in the example above.

    There are some other issues in your code around repeating my $info2, etc. -- you might want to also try use warnings as a learning tool

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      Thanks to everyone for your help. It would appear I have some reading to do. I do have one more question though. Should I just call the mailoff sub from the IP_check sub instead of from the main body of the script? Would this be better?

        They're trying to explain you that you're using subs that are actually portions of code factorized away, but you're using them to manipulate "global" variables as a side effect. Sometimes it is sensible to do so, but it's rare: most often, and I mean most, you want to exploit a distinctive feature of them, precisely the one that makes them "functions", i.e. you give something to them to eat, and they spit out a result for you.

        Different languages use different mechanisms for parameter passing. Perl uses a special array called @_: there are several idioms in connection with it. To go on, I heartily recommend you read perldoc perlsub - you wrote that you're "self taught", so please (do a favour to yourself and) continue your self-teaching on that page!

Re: passing variables between subroutines
by Grygonos (Chaplain) on Jan 16, 2006 at 15:42 UTC
    foo("test","test2"); sub foo { my ($param_one,$param_two) = @_; print $param_one . "-".$param_two."\n"; }
    you pull them off the @_ array you can shift them off the array individually or assign them in one shot as shown above. <edit> alternatively, if you have only one parameter you can do the following my $param_one = shift @_</edit>

      You don't need to do anything different if you have only one parameter.

      my ($param) = @_;

      Remember, you have to use the () there to create list context. Otherwise the @_ will be in scalar context (i.e. number of items in the array).

      I prefer this style, because it looks like C, Java, Ruby, etc. It also looks more like math, e.g. f(x, y, z).

      you pull them off the @_ array you can shift them off the array individually or assign them in one shot as shown above. <edit> alternatively, if you have only one parameter you can do the following my $param_one = shift @_</edit>

      As bunnyman correctly pointed out there's no real difference between the case of one and of many parameters respectively. What is to be said here is that:

      1. shift is customary when passing just one parameter or when "singling" out one, e.g. typically in OO:
        my $self=shift; my ($foo, $bar, $baz)=@_;
        even though
        my ($self $foo, $bar, $baz)=@_; # would be just as fine.
      2. @_ is the implicit topic of shift, so, while you generally shift @an_array, the common and recommendable idiom here is
        my $param_one=shift;