Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Accessing variables in an external hash without eval

by victorz22 (Sexton)
on May 17, 2017 at 03:00 UTC ( [id://1190415]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks, I am trying to load in separate files and access variables inside a hash in those files. The function I am making needs to take in the file name, file location, and two keys of that hash as parameters. The files will change so I am trying to code this without 'require'ing them in the beginning of the program. I tried using eval but I get an error where when I try and store the hash data in an array, the data ends up being from the wrong file.

#The external hash looks like this %hash = ( #scope changes Scope => { model => [ data I need another row of needed data more data I need ] } IrreleventScope =>{ #don't need this stuff } )
#My function looks like this sub cycleUsesForLibs{ my ($fileDirectory, $scopeName, $fileName, $modelName) = @_; chdir($fileDirectory); open FILE_HANDLE, "$fileName" or die "couldn't open file '$fileNam +e'. \n"; my $loadedFile = join "", <FILE_HANDLE>; close FILE_HANDLE; eval $loadedFile; my @hashData = $hash{$scopeName}{$modelName}; my @dereferencedData; foreach my $line (@hashData){ @dereferencedData = @$line; } print "Data: \n"; print Dumper @dereferencedData; print "\n"; #die "Couldn't interpret the file ($loadedFile) that was given.\nE +rror details follow: $@\n" if $@; }

Replies are listed 'Best First'.
Re: Accessing variables in an external hash without eval
by afoken (Chancellor) on May 17, 2017 at 07:59 UTC

    Consider using a different storage format. I would choose JSON:

    FormatHuman readableArbitary structures
    (See update below)
    8-bit cleanVersion independantCross languageMay execute code from fileUnexpected network accessMemory usage attackComments
    Perl source code (generated manually or by tools like Data::Dumper)kind ofyesyesmostlyno (only perl can parse Perl)yesby executable codeby executable codeyes
    Storablenoyesyesno (depends on Perl version, limited compatibility with other versions)nonononono
    XMLyesyesno
    • \x00 is illegal in XML, workaround like base64 required
    • any amount of whitespace is often treated as a single space (CDATA required)
    yesyesnoyesyesyes
    YAMLyes (but with strange rules)yesyesyesyesyes (may be disabled)by executabe codeby executabe codeyes
    JSONyesyesyesyesyesnononono (but some parsers allow Javascript or shell comments)
    INIyesno, only HoHno (escaping rules depend on reader and writer)yesyesnononoyes
    CSVyesno, only 2D-Array (AoA)no (escaping rules depend on reader and writer)yesyesnononono

    See also Re^4: The safety of string eval and block eval. and Re^2: Storing state of execution


    Updates:

    "Arbitary structures" was not meant as arbitary as I wrote, thanks tobyink++. It should read something like "any mix of scalars, arrays, and hashes, without circular references, handles, code references, globs".

    "Memory usage attack" means that either the parsed file uses significantly more memory (several orders of magnitute) than the file size, or parsing the file may execute code that allocates much memory.

    "Unexpected network access" means either that parsing the file completely and correctly may require reading additional data from the internet, or parsing the file may execute code that accesses the network.

    "8 bit clean" means that any binary data may be stored and fetched.

    Added comments column

    Added Data::Dumper

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      JSON cannot store "arbitrary structures". Not unless your definition of "arbitrary" is extremely limited. It can't contain cyclic references for example. Something like this can be represented in Perl code, Storable, YAML, and (using IDREF) probably in XML, but not JSON…

      use strict; use warnings; use Data::Dumper qw(Dumper); use YAML::XS qw(Dump); my $data = do { my $child = {}; my $parent = { child => $child }; $child->{parent} = $parent; }; print "#### Perl ####\n"; print Dumper($data), "\n"; print "#### YAML ####\n"; print Dump($data), "\n";
        JSON cannot store "arbitrary structures". Not unless your definition of "arbitrary" is extremely limited. It can't contain cyclic references for example.

        You are right.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Accessing variables in an external hash without eval
by Athanasius (Archbishop) on May 17, 2017 at 04:48 UTC

    Hello victorz22,

    I tried using eval...

    Actually, this is a job for the do EXPR syntax (see do). With data file dir/hash_file.txt under the script’s directory and its contents corrected as per kevbot’s answer, the following script:

    use strict; use warnings; use Data::Dumper; our %hash; cycleUsesForLibs('./dir', 'Scope', 'hash_file.txt', 'model'); sub cycleUsesForLibs { my ($fileDirectory, $scopeName, $fileName, $modelName) = @_; chdir $fileDirectory; do $fileName; my @hashData = $hash{$scopeName}{$modelName}; my @dereferencedData; push @dereferencedData, @$_ for @hashData; print "Data: \n", Dumper(@dereferencedData); }

    gives this output:

    14:35 >perl 1777_SoPW.pl Data: $VAR1 = 'data I need'; $VAR2 = 'another row of needed data'; $VAR3 = 'more data I need'; 14:36 >

    Note:

    1. With use strict in place (as it always should be!), it’s necessary to declare our %hash before referencing it in $hash{$scopeName}{$modelName}.
    2. You want @dereferencedData to contain all the lines of data, not just the last one, so you need to push these onto the array. The code you show just overwrites the array on each iteration of the foreach loop.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Thank you however the code didn't work, i get an error saying i cant use an undefined value as an array reference on the line with the push statement. Also because my function will be called multiple times will the global %hash variable be overwritten? Im not sure how the global will work for multpile different hashes all using one global.

        i get an error saying i cant use an undefined value as an array reference on the line with the push statement.

        Then the hash wasn’t correctly read-in from the data file. Have you fixed the format of the data as kevbot showed? Without further details (see The SSCCE), it’s impossible to know where the problem lies.

        <Update>
        Add the error handling code shown at the end of the do documentation to determine whether the do statement (1) failed to read the file, (2) read the file but failed to parse (compile) it, or (3) successfully read and parsed the file. Then print out the contents of %hash to see exactly what data was read in.
        </Update>

        Also because my function will be called multiple times will the global %hash variable be overwritten?

        Yes, of course, each time the subroutine is called the do $fileName; statement will overwrite %hash with the data in the indicated file.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Accessing variables in an external hash without eval
by kevbot (Vicar) on May 17, 2017 at 03:20 UTC
    The hash you provided is not legal perl syntax. Try changing your original hash data to
    %hash = ( #scope changes Scope => { model => [ 'data I need', 'another row of needed data', 'more data I need' ] }, IrreleventScope => { #don't need this stuff } );

      Thanks for catching my error but that isn't my actual hash it was only an example. I can't post the actual one but it does have a semicolon as well as the 1 at the end of the file.

Re: Accessing variables in an external hash without eval
by huck (Prior) on May 17, 2017 at 03:33 UTC

    As kevbot said your example is flawed.

    This works for me

    use strict; use warnings; use Data::Dumper; my %hash; my $loadedFile = join "", <DATA>; { no strict; eval ($loadedFile); if ($@) {print 'eval error:'.$@."\n"; } } my @hashData = $hash{Scope}{model}; my @dereferencedData; foreach my $line (@hashData){ @dereferencedData = @$line; } print "Data: \n"; print Dumper @dereferencedData; print "\n"; __DATA__ %hash = ( #scope changes Scope => { model => [ 1,2,3,4,5,6 ] }, IrreleventScope =>{ a=>'b' } ) ;
    Result
    Data: $VAR1 = 1; $VAR2 = 2; $VAR3 = 3; $VAR4 = 4; $VAR5 = 5; $VAR6 = 6;
    Notice the addition of a semi to the end of your example data

      Thank you but the solution didnt work for me, I still get an uninitialized variable for my @hashData. However i was able to get your code to load in $loadedFile.

        If it did not work for you (but you don't explain i which respect it did not work) and worked for huck, then it is likely there's something wrong in the code you did not show, perhaps in the hash initialization you replaced with pseudo-code.

        I still get an uninitialized variable for my @hashData.

        Then it seems obvious that you're not loading a value with this line:

        my @hashData  = $hash{Scope}{model};

        Side note: This should probably read more like:

        my @hashData  = @$hash{Scope}{model};

        Never assume there is data where you expect it to be when it is coming from an outside source. Check it first. For example:

        my @hashData = (); if (!defined $hash{Scope}{model}) { print "Scope and model missing!\n"; } else { @hashData = @$hash{Scope}{model}; }

        ...or something of that ilk.

Re: Accessing variables in an external hash without eval
by NetWallah (Canon) on May 17, 2017 at 04:38 UTC
    @hashdata should really be a scalar.
    This simplifies the code (It could be similified even more if you did not need to expand into @dereferencedData):
    use strict; use warnings; use Data::Dumper; my %hash; my $loadedFile = do {local $/=undef; <DATA>}; { no strict; eval ($loadedFile); if ($@) {print 'eval error:'.$@."\n"; } } my $hashData = $hash{Scope}{model}; my @dereferencedData = @$hashData; print "Data: \n", Dumper (\@dereferencedData), "\n"; __DATA__ %hash = ( #scope changes Scope => { model => [ 'data I need', 'another row of needed data', 'more data I need' ] }, IrreleventScope =>{ a=>'b' } );
    Output:
    Data: $VAR1 = [ 'data I need', 'another row of needed data', 'more data I need' ];

                    Once it hits the fan, the only rational choice is to sweep it up, package it, and sell it as fertilizer.

      Thanks for the tip on using a scalar instead of an array. I still can't get the code to work, the error i get is that $hashData doesn't get initialized so there is something wrong with that call, however $loadedFile does get loaded in properly, thank you.

        Show us the offending line(s) of code, and the data you are working with, and, most importantly, the error message!

                        Once it hits the fan, the only rational choice is to sweep it up, package it, and sell it as fertilizer.

Re: Accessing variables in an external hash without eval
by kcott (Archbishop) on May 17, 2017 at 05:50 UTC

    G'day victorz22,

    [Although you've only been here a while, you've made quite a few posts and should be aware of both "How do I post a question effectively?" and "SSCCE". Even when you can't post real data, you can post dummy data that's representative of what you're working with. Posting the type of thing you have here helps no one.]

    You can do what you want ("without eval") by using require.

    Instead of repeatedly doing I/O every time you want to access some value from your data, I recommend you read the file and capture the data once; it can then be used as many times as you want (without further I/O).

    Furthermore, rather than changing directory and then accessing a file, I'd suggest accessing a path directly. You can use File::Spec to do this.

    I dummied up this data for you:

    $ cat pm_1190415/external/external_hash %hash = ( scopeX => { modelA => [qw{XA XA XA}], modelB => [qw{XB XB XB}], }, scopeY => { modelA => [qw{YA YA YA}], modelB => [qw{YB YB YB}], }, scopeZ => { modelA => [qw{ZA ZA ZA}], modelB => [qw{ZB ZB ZB}], }, ); 1;

    This script shows techniques for achieving all of my recommendations:

    #!/usr/bin/env perl -l use strict; use warnings; use File::Spec; my ($dir, $file) = qw{pm_1190415/external external_hash}; my $extern_hash = get_extern_hash($dir, $file); print "@{$extern_hash->{scopeX}{modelB}}"; print "@{$extern_hash->{scopeZ}{modelA}}"; sub get_extern_hash { my $path = File::Spec::->catfile(@_); our %hash; require $path; return \%hash; }

    Here's the output:

    XB XB XB ZA ZA ZA

    — Ken

      You can do what you want ("without eval") by using require.

      That's not strictly "without eval". All of eval STRING, do FILENAME, require FILENAME, require MODULE, use FILENAME, use MODULE, and evalbytes STRING (since v5.16) finally evaluate a string or file contents as perl code and execute it. See Re^4: Storing state of execution.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
Re: Accessing variables in an external hash without eval
by KurtZ (Friar) on May 17, 2017 at 07:19 UTC
    I think in reality you have a my in front of the %hash, probably inside a block.

    You should show us the real code. And please try to isolate the problem step by step.

    On a side note, you can use "do filepath" instead of all the reading and eval stuff.

Re: Accessing variables in an external hash without eval
by Anonymous Monk on May 17, 2017 at 06:38 UTC

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (4)
As of 2024-04-24 03:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found