Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Interpolating a string into list context.

by hangon (Deacon)
on Nov 13, 2007 at 06:37 UTC ( [id://650446] : perlquestion . print w/replies, xml ) Need Help??

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

Hi Monks,

I'm trying to interpolate a string of substrings to load into an array, and wanted to do it in 1 or 2 lines without loading a module. It seems like there should be a better way to DWIM. Here's what I have. Thanks for any ideas.

# the substrings might be single or double qouted my $string = q("one","two","three"); # the obvious variations on direct assignment don't work: my @arr1 = ("$string"); my @arr2 = "$string"; my @arr3 = {"$string"}; # etc # these work, but: # I don't want to use string eval my @array = eval $string; # and this kind of stuff just gets ugly $string =~ s/^[\'\"]//; $string =~ s/[\'\"]$//; my @array2 = split(/[\'\"],[\'\"]/, $string); # is there a better way? use Data::Dumper; print "\nThis is what I'm looking for:\n"; print Dumper \@array; print "\nNot This: \n"; print Dumper \@arr1;

UPDATE: Thanks for the help everyone. I was finally able to set aside my obsessive compulsion about this and finish the rest of my code. The CSV spec I'm dealing with is well defined, so I'll tinker with some of the regex ideas a bit, but may end up looking for a lightweight CSV parser on CPAN.

Replies are listed 'Best First'.
Re: Interpolating a string into list context.
by grinder (Bishop) on Nov 13, 2007 at 08:52 UTC

    Well, with far less code, you could do the following:

    my $string = q("one","two","three"); my @array = split( /"(?:,")?/, $string );

    This will, however, create an empty element at the beginning of the list (the stuff before the opening quote of "one"). You could remove it afterwards with shift @array.

    You could attempt to get rid of that extra element with a negative look-behind assertion:

    my @array = split( /(?<!^)"(?:,")?/, $string );

    But that has the undesirable property of returning a string equivalent to:

    my @array = qw( "one two three );

    Again, this is trivial to fix:

    $array[0] =~ s/^"/;

    But, as you can see, when what appears as very simple code turns out to require some weird make-work code to solve the "almost-there" case, there's something wrong. This is what is known as code smell. I think what you really want is to load a module, such as Text::CSV_XS (or Text::CSV), and be done with it.

    Even if you think it's overkill in this instance, it doesn't cost you much to learn how to use it. In the long run, you will have learnt how to use it in a simple scenario, and that will pay off later when you have some much hairier CSV problems, and you might risk trying to extend this code a bit more, rather than using a proven module... and that's when you'll pay the full price of not having learnt it now when it was easy.

    • another intruder with the mooring in the heart of the Perl

Re: Interpolating a string into list context.
by GrandFather (Saint) on Nov 13, 2007 at 06:58 UTC

    If the strings don't contain commas then you could:

    my $string = q("one","two","three"); my @array = map {s/^['"](.*)['"]$/$1/ ? $1 : $_} split ',', $string; print "@array";

    Prints:

    one two three

    Perl is environmentally friendly - it saves trees
Re: Interpolating a string into list context.
by Somni (Friar) on Nov 13, 2007 at 07:02 UTC
    The simple solution would be to split on comma, then strip quotes on each resulting value. However, I suspect you really want to ignore commas inside quotes, and escaped quotes. This becomes less trivial.

    Text::Balanced has some general-purpose parsing routines for extracting quoted text. Regexp::Common has some regexes for matching quoted text, while handling escaped quotes. You would then have to write your own little parser to keep applying the regex.

    I realize you asked to do this without the aid of modules. However, the task is much easier with one of these, and for non-trivial cases reinventing your own wheel is not advisable.

Re: Interpolating a string into list context.
by stark (Pilgrim) on Nov 13, 2007 at 08:39 UTC

    Your format seems to be simple quoted csv. So you can use one of the csv-modules from cpan to parse the string into fileds.

    I would suggest Text::CSV. Have a look at the example that comes with the documentation. It is not shorter than your regex-split sollution - but more stable.

    Update: grammar fix

Re: Interpolating a string into list context.
by johngg (Canon) on Nov 13, 2007 at 10:22 UTC
    You could do this with a global match but it probably fails your "just gets ugly" criterion.

    use strict; use warnings; my $str = q{"one","two", "not three" , ' four',"five " }; my @arr = $str =~ m {(?x) (?<=['"]) ([^,'"]+) (?=['"]) }g; print qq{-->$_<--\n} for @arr;

    Here's the output.

    -->one<-- -->two<-- -->not three<-- --> four<-- -->five <--

    This method is going to fall down pretty quickly as your data gets more complex and I agree with others who have suggested some sort of CSV module to tackle the problem.

    Cheers,

    JohnGG

Re: Interpolating a string into list context.
by jbert (Priest) on Nov 13, 2007 at 11:25 UTC
    This probably isn't a good idea. It does work though.
    #!/usr/bin/perl use warnings; use strict; my $str = q("string with a comma,","and some spaces", "three"); my @array; eval <<"EVILEVAL"; \@array = ( $str ); EVILEVAL print "ELT: $_\n" for @array;
    Now I'll go away and right 100 times, "I will not execute my data. I will not execute my data. I will not...".

    More seriously, the CSV approaches people have mentioned are probably the way to go.

      Actually, I did do this as a temporary fix so I could get on with the rest of the project. Here's my penance:

      1. I will not execute my data. 2. I will not execute my data. ... ... ... 100. I will not execute my data.
        Awesome. I hope you one day repent, but we all know there is nothing so permanent as a temporary fix.

        More seriously, make sure your aren't getting your strings from a place where someone might be able to inject any code.

        As I'm sure you know, this is like SQL injection but only more fun since you have a whole perl interpreter to play with.

Re: Interpolating a string into list context.
by chrism01 (Friar) on Nov 13, 2007 at 23:27 UTC
    Simplistic but without module

    $var1 = q("one","two","three",'four'); for $var2 ( split(/,/, $var1) ) { $var2 =~ s/"|'//g; push(@arr1, $var2); } print "@arr1\n"; one two three four
    More complex data may require a CSV module as recommended above.

    Cheers
    Chris