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

Is it possible to pass a subroutine a string with a variable in it, and act on that string within the subroutine and update the embedded variable?

For example, pass to a subroutine file with a list of symbols and a URL with a variable for the undefined symbol embedded in it, like the following.

When I run the following code, it tries to execute and download the file as expected, but $symbol info in the file directory and in URL are undefined.

my $testfile="/home/user/symbol_list"; my $wget_url="/home/user/sym_dir/${symbol}_2013 'http://www.downloadsi +te.com/${symbol}.dat'"; &get_symbol_data($testfile,$wget_url); sub get_symbol_data { open (FILE, $_[0]) || die "Can't open $_[0] $!\n"; @sym_array = <FILE>; close <FILE>; foreach $symbol (@sym_array) { chomp $symbol; `usr/bin/wget -O $_[1]`; } }

Replies are listed 'Best First'.
Re: Subroutine and variable passing
by kcott (Archbishop) on Aug 24, 2013 at 11:22 UTC

    G'day tman77,

    Welcome to the monastery.

    Had you been using strict, you would've received a strict vars error explaining the problem. ${symbol} is neither declared nor defined so $wget_url evaluates to:

    "/home/user/sym_dir/_2013 'http://www.downloadsite.com/.dat'"

    Had you declared, but not defined, ${symbol}, Perl would've warned you of that as well, if you'd been using warnings. So, unless you have a very good reason not to, always put

    use strict; use wanings;

    at the start of your code. Typing those few characters and being advised immediately by Perl of problems with your code is obviously a lot less work than having to post a question in a forum; and, also obviously, the feedback is a lot quicker.

    I don't think your approach to this is a particularly good one; although, without knowing more about the context of your complete code, it's difficult to specifically advise what would be better. I'd probably have used some sort of token in place of ${symbol} and substituted that with the symbols from the file; something along these lines:

    $ perl -Mstrict -Mwarnings -le ' my $wget_url = "/some/path/_SYMBOL__2013 http://example.com/_SYMBO +L_.dat"; my @symbols = qw{A B C}; for (@symbols) { (my $sym_url = $wget_url) =~ s/_SYMBOL_/$_/g; print $sym_url; } ' /some/path/A_2013 http://example.com/A.dat /some/path/B_2013 http://example.com/B.dat /some/path/C_2013 http://example.com/C.dat

    A few other issues with your code:

    • Don't prepend an ampersand ("&") to your function names unless you have a good reason for doing so and understand what that reason is. Adding an "&" is possibly not doing what you think it is and may have unexpected side-effects. See "perlsub - Perl subroutines" for more details.
    • As it doesn't look like @sym_array changes, move the I/O code (that reads its values) out of the get_symbol_data subroutine: you don't need to populate @sym_array on every call to get_symbol_data().
    • Don't terminate your die message with a newline. Perl appends file and line information to your message (that you normally want) as well a newline anyway. Adding your own newline stops this from happening. See the die documentation for details.
    • It's better to use the 3-argument form of open. See the open documentation for usage examples and reasons for doing this.
    • Avoid working directly with $_[0], $_[1], etc in subroutines. You run the risk of altering the original data passed to the subroutine, possibly in subtle and unexpected ways that result in bugs that are hard to track down. Read arguments preferably like this "my ($x, $y, $z) = @_;"; while you can use "my $x = shift;" for single arguments, subroutines have a habit of requiring additional arguments as they mature and you end up with this common bug "my ($x, $y) = shift;", which doesn't happen if your wrote "my ($x) = @_;" in the first place.

    -- Ken

Re: Subroutine and variable passing
by boftx (Deacon) on Aug 24, 2013 at 06:12 UTC

    Is there a reason you have to define $wget_url outside of the subroutine? If not, then this change should allow it to work as you desire:

    my $testfile="/home/user/symbol_list"; &get_symbol_data($testfile); sub get_symbol_data { open (FILE, $_[0]) || die "Can't open $_[0] $!\n"; @sym_array = <FILE>; close <FILE>; foreach $symbol (@sym_array) { chomp $symbol; my $wget_url=qq~/home/user/sym_dir/${symbol}_2013 'http://www.downl +oadsite.com/${symbol}.dat'~; `usr/bin/wget -O $wget_url`; } }

    The qq~...~ allows all occurrences of $symbol to be properly interpolated, even inside single quotes in a string assignment.

Re: Subroutine and variable passing
by Anonymous Monk on Aug 25, 2013 at 17:10 UTC
    I understand your question, but I'm not really sure what your code is trying to do.

    Yet I'll try proposing an alternative solution: Replace it with sprintf?

    my $url = [ 2, "/home/user/sym_dir/%s_2013 'http://www.downloadsite.co +m/%s.dat'" ]; get_symbol_data($url); sub get_symbol_data { my ($base_url) = @_; my ($repeats, $fmt_string) = @$base_url; my @sym_array = ('foo', 'bar', 'baz'); for my $symbol (@sym_array) { my $url = sprintf($fmt_string, ( $symbol ) x $repeats); print $url, "\n"; } }

      I like the approach taken by this proposed solution with one exception: I would forego the $repeats variable and just list $symbol twice in the sprintf argument list. While the former might be more concise, I feel the latter would be more understandable to those who might not be as familiar with the full power of Perl's string processing abilities.

Re: Subroutine and variable passing
by boftx (Deacon) on Aug 24, 2013 at 05:49 UTC

    Update: deleted incorrect response.