So I wrote some "throwaway code" (where "TFD" stands for "Temporary for debugging"):
and was surprised to find that only a single asterisk was printed out for those users which had the permissions set.printf "TFD> %32.32s %16.16s %s %s\n", $user, $pass, ($b_admin? '*': ' ') x 8, ($b_reports? '*': ' ') x 8;
I had expected that the ternary operator '?:' should have produced either an asterisk '*' or a space ' ', and then that character would have been expanded to a string of 8 characters.
After thinking about it for a minute and not coming up with any answers, it seemed sensible to write a simplified test program to investigate. My first pass was this:
As expected, the "problem" was present, as the output reveals a single asterisk where I thought I should see 8 of them:#!/usr/bin/perl -w # Strict use strict; use warnings; # Libs use Data::Dumper; # User-defined my $pusers = { jack => 1, jill => 0, tom => 1, dick => 0, harry => 0, thelma => 0, louise => 1, }; # Main program my $mark; foreach my $user (sort keys %$pusers) { my $flag = $pusers->{$user}; printf "%10.10s %s\n", $user, ($flag? '*': ' ') x 8; }
Next I tried doing what I was sure would work, beside what was failing, and changed the main program to:dick harry jack * jill louise * thelma tom *
Sure enough, the termary operator was "working" when the variable $mark was assigned ahead of time, but "failing" when used all on one line:# Main program my $mark; foreach my $user (sort keys %$pusers) { my $flag = $pusers->{$user}; printf "%10.10s %s\n", $user, ($flag? '*': ' ') x 8; $mark = $flag? '*': ' '; printf "%10.10s %s\n", $user, $mark x 8; }
Realizing that I wasn't sure what arguments were being passed to printf, I wrote a wrapper subroutine myprintf(), to intercept and display those arguments:dick dick harry harry jack * jack ******** jill jill louise * louise ******** thelma thelma tom * tom ********
Jackpot! It's now fairly clear from the output what's happening:# Subroutines sub myprintf { my (@args) = @_; printf "TFD> %s\n", Dumper(\@args); } # Main program my $mark; foreach my $user (sort keys %$pusers) { my $flag = $pusers->{$user}; $mark = $flag? '*': ' '; myprintf "%10.10s %s\n", $user, $mark x 8; myprintf "%10.10s %s\n", $user, ($flag? '*': ' ') x 8; }
In the former case, the scalar variable $mark along with the repetition operator 'x' was producing an 8-character string. In the latter case, however, the 'x' operator was being applied to a list, and thus producing a list of 8 separate asterisks '*'.TFD> $VAR1 = [ '%10.10s %s ', 'jack', '********' ]; TFD> $VAR1 = [ '%10.10s %s ', 'jack', '*', '*', '*', '*', '*', '*', '*', '*' ];
I was now able to fix the original problem by forcing scalar context on my list, with something like:
printf "TFD> %32.32s %16.16s %s %s\n", $user, $pass, scalar ($b_admin? '*': ' ') x 8, scalar ($b_reports? '*': ' ') x 8;
Or even more simply:
printf "TFD> %32.32s %16.16s %s %s\n", $user, $pass, ''.($b_admin? '*': ' ') x 8, ''.($b_reports? '*': ' ') x 8;
The point of all this is that Perl is complex, and that complexity can make it a difficult language to learn. Understanding and writing code using the various contexts in Perl, like many other Perl constructs (eg. the ternary operator '?:' and the repetition operator 'x'), is by no means an easy task. But it's well worth the effort because, in the end, you have the advantage of a richer, more powerful language with which to do your work.
And the next time I need to construct a complicated repetition within a string, I'll think twice about which context I'm in.
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |