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

Three questions in one node for you wise old owls today, so make sure you upvote this three days running ;)

1) Say I want to read a file which may have some lines commented out. I want one array with every line in it, whether it's commented out or not (and the '#' removed if it is), and one array with every line which isn't commented out in it, with any comments after the data being removed. This would be explained better like so:
my @conditional_lines = <DATA>; my @all_lines = @conditional_lines; chomp(@all_lines); chomp(@conditional_lines); map { s/^#// } @all_lines; map { s/#.*$// } @all_lines; map { s/#.*$// } @conditional_lines; print @all_lines; print @conditional_lines; __DATA__ #my commented out line a standard line a line with a # comment at the end
That, however, feels horrible, and doesn't take account of blank lines (ideally they would not be added to the array at all). (Aside: Also, isn't map supposed to return a list, so shouldn't I be able to do @all_lines = map { s/^#// } @conditional_lines and that would copy the array with the comments removed in one go, rather than having to explicitly = copy it?)
A while loop is probably the sensible solution:
while (<DATA>) { chomp(); my $copy = $_; $copy =~ s/^#//; $copy =~ s/#.*$//; push(@all_lines, $copy); s/#.*$//; next unless ($_); push(@conditional_lines, $_); } print @all_lines; print @conditional_lines; __DATA__ #my commented out line a standard line a line with a # comment at the end
But that feels horrible also. How would you do it?

2) I have this line that I was rather proud of: warn("$0 started running at ", scalar(localtime()), ' with switches ', defined(my $switches_text = join ', ', keys %switches) ? $switches_text : "(none)", "\n"); Fails, though. Apparently I need to declare $switches_text with my earlier, because it works if I put a my $switches_text on the line above. Anyone got any idea why? I was hoping I could fit it all onto one line.

3) What's the best way to structure the code in the main scope. Should it be a series of subroutine calls, which then abstract further? Or should there be a lot of code in the main scope? I feel it might help maintainability if most of the code was in subs.
Thanks for any aid.

--
my @words = grep({ref} @clever);
sub version { print "I cuss your capitalist pig tendencies bad!\n" }

Replies are listed 'Best First'.
Re: duplicate $_ while reading files; my compactness; code structure
by merlyn (Sage) on Oct 03, 2001 at 20:22 UTC
    1) Say I want to read a file which may have some lines commented out. I want one array with every line in it, whether it's commented out or not (and the '#' removed if it is), and one array with every line which isn't commented out in it, with any comments after the data being removed. This would be explained better like so:
    my @lines = <DATA>; my @sans_hashes = map { /^#(.*)/s ? $1 : $_ } @lines; my @without_comment = grep !/^#/, @lines;

    -- Randal L. Schwartz, Perl hacker

Re: duplicate $_ while reading files; my compactness; code structure
by merlyn (Sage) on Oct 03, 2001 at 20:30 UTC
    2) I have this line that I was rather proud of: warn("$0 started running at ", scalar(localtime()), ' with switches ', defined(my $switches_text = join ', ', keys %switches) ? $switches_text : "(none)", "\n"); Fails, though. Apparently I need to declare $switches_text with my earlier, because it works if I put a my $switches_text on the line above. Anyone got any idea why? I was hoping I could fit it all onto one line.
    warn("$0 started running at ".localtime().' with switches ', (%switche +s ? join ', ', keys %switches : "(none)"), "\n");

    -- Randal L. Schwartz, Perl hacker

Re: duplicate $_ while reading files; my compactness; code structure
by Fletch (Bishop) on Oct 03, 2001 at 20:46 UTC

    map does return a list, but it returns the value of the expression or block. In your case this would be the result of the s///, which would be a true or false value (1 or undef) depending if the substitution succeeded or failed. Also keep in mind that $_ is an alias for each element of the list being map'd over, similar to how $_ behaves inside of a foreach. This means that your substitution is modifying your original list.

    @a = qw(a b); @b = map { s/([ac])/uc $1/e } @a; print join( ",", map {length($_) ? $_ : q{""}} @{$_} ), "\n" foreach \@a, \@b;

    As for using map in void context (throwing away its return value) is considered bad form in many circles. Either use foreach to iterate over the list (using push to build a new list as you go), or use a temporary copy to do the subistitution on inside the map.

    @mod_lines = map { (my $tmp = $_) =~ s/^#.*//; $tmp } @all_lines;
Re: duplicate $_ while reading files; my compactness; code structure
by broquaint (Abbot) on Oct 03, 2001 at 20:50 UTC
    Yes the map and grep functions are your friends (good friends) -
    my @some = grep { !/^\s*$/ } (my @most = map { s/#.*$//; $_ } <FILE>);
    Although this does make assumptions like comments not being in the middle of quotes and the like, otherwise you'll want some slightly fancier parsing than the above. Also, did you *just* want to get rid of the hashes (like you say), or the hashes *and* the comments (like you code)?
    HTH

    broquaint

    Update: yes running the code first might be an idea... now works proper.
    Update 2: sometimes they come back... got rid of @all as I didn't *really* need it, as merlyn indirectly illustrates below :o)

      my @all = <FILE>; my @some = grep { !/^\s*$/ } (my @most = map { s/#.*$//; $_ } @all);
      Beware... that last map alters @all. It's very misleading.

      If you're going to "copy and change", a nice idiom (which I've talked about here in the past) is:

      s/#.*$// for my @most = @all;
      Although this isn't actually correct in the context of the original question. Just posting a better way to do what you ended up accidentally doing.

      -- Randal L. Schwartz, Perl hacker

Re: duplicate $_ while reading files; my compactness; code structure
by BrentDax (Hermit) on Oct 05, 2001 at 11:13 UTC
    It won't be as pretty as, say, Randal's, but whatever...

    @code_only=grep {/./} (@everything=map {/^([^#]*)/} <INPUT>);

    =cut
    --Brent Dax
    There is no sig.