Bourne Shell: How to concatenate variables that need to be evaluated? - sh

I'm having trouble figuring out a way to properly concatenate several variables together. The idea is to collect several items over time (in this case "foo", "bar", and "baz") and then concatenate together into one string (ex: X = "foo bar baz").
The following is the code I have put together so far:
#!/bin/sh
N=0
# assign foo
eval "DATA${N}='foo'"
eval "echo First value is: \$DATA$N" # First value is: foo
N=`expr $N + 1`
# assign bar
eval "DATA${N}='bar'"
eval "echo Next value is: \$DATA$N" # Next value is: bar
N=`expr $N + 1`
# assign baz
eval "DATA${N}='baz'"
eval "echo Last value is: \$DATA$N" # Last value is: baz
for i in 0 1 2
do
# concatenate foo bar and baz into one variable
done
The comment in the for-loop is the area I'm having trouble right now. Any help would be much appreciated. Thanks!

You just have to escape the $ operator for the first eval pass:
blob=
for i in 0 1 2
do
eval blob="\$blob\$DATA${i}"
done

Related

Separate command-line arguments into two lists and pass to programs (shell)

What I want to do is take a list of command-like arguments like abc "def ghi" "foo bar" baz (note that some arguments are quoted because they contain spaces), and separate them out into two lists of arguments which then get passed to other programs that are invoked by the script. For example, odd-numbered arguments to one program and even-numbered arguments to another program. It is important to preserve proper quoting.
Please note, I need a solution in pure Bourne Shell script (i.e., sh not bash or such). The way I'd do this in Bash would be to use arrays, but of course the Bourne Shell doesn't have support for arrays.
At the cost of iterating over the original arguments twice, you can define a function that can run a simple command using only the even or odd arguments. This allows us to use the function's arguments as an additional array.
# Usage:
# run_it <cmd> [even|odd] ...
#
# Runs <cmd> using only the even or odd arguments, as specified.
run_it () {
cmd=${1:?Missing command name}
parity=${2:?Missing parity}
shift 2
n=$#
# Collect the odd arguments by discarding the first
# one, turning the odd arguments into the even arguments.
if [ $# -ge 1 ] && [ $parity = odd ]; then
shift
n=$((n - 1))
fi
# Repeatedly move the first argument to the
# to the end of the list and discard the second argument.
# Keep going until you have moved or discarded each argument.
while [ "$n" -gt 0 ]; do
x=$1
if [ $n -ge 2 ]; then
shift 2
else
shift
fi
set -- "$#" "$x"
n=$((n-2))
done
# Run the given command with the arguments that are left.
"$cmd" "$#"
}
# Example command
cmd () {
printf '%s\n' "$#"
}
# Example of using run_it
run_it cmd even "$#"
run_it cmd odd "$#"
This might be what you need. Alas, it uses eval. YMMV.
#!/bin/sh
# Samples
foo() { showme foo "$#"; }
bar() { showme bar "$#"; }
showme() {
echo "$1 args:"
shift
local c=0
while [ $# -gt 0 ]; do
printf '\t%-3d %s\n' $((c=c+1)) "$1"
shift
done
}
while [ $# -gt 0 ]; do
foo="$foo \"$1\""
bar="$bar \"$2\""
shift 2
done
eval foo $foo
eval bar $bar
There's no magic here -- we simply encode alternating arguments with quote armour into variables so they'll be processed correctly when you eval the line.
I tested this with FreeBSD's /bin/sh, which is based on ash. The shell is close to POSIX.1 but is not necessarily "Bourne". If your shell doesn't accept arguments to shift, you can simply shift twice in the while loop. Similarly, the showme() function increments a counter, an action which can be achieved in whatever way is your favourite if mine doesn't work for you. I believe everything else is pretty standard.

(4 + sub) not equals to (sub + 4)?

(edit) TL;DR: my problem was that I though the Win32 API defines were true integer constants (as in the platform SDK headers) while the Win32 Perl wrapper defines them as subs. Thus caused the one-liner parsing misunderstood.
While testing in a one-liner a call to Win32::MsgBox, I am puzzled by the following : giving that the possible arguments for MsgBox are the message, a sum of flags to chose the kind of buttons (value 0..5) and message box icon "constants" (MB_ICONSTOP, ...) and the title
calling perl -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQUESTION, hello" gives the expected result
while the looking similar code perl -MWin32 -e"Win32::MsgBox world, MB_ICONQUESTION+4, hello" is wrong
I first though that it comes from my lack of parenthesis, but adding some perl -MWin32 -e"Win32::MsgBox (world, MB_ICONQUESTION+4, hello)" gives exactly the same wrong result.
I tried with a colleague to dig deeper and display the parameters that are passed to a function call (as the MB_xxx constants are actually subs) with the following code
>perl -Mstrict -w -e"sub T{print $/,'called T(#'.join(',',#_).'#)'; 42 }; print $/,'results:', join ' ,', T(1), T+1, 1+T"
that outputs
called T(#1#)
called T(##)
called T(#1,43#)
results:42 ,42
but I can't understand why in the list passed to join() the args T+1, 1+T are parsed as T(1, 43)...
B::Deparse to the rescue:
C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, MB_ICONQUETION+4, hello"
use Win32;
Win32::MsgBox('world', MB_ICONQUESTION(4, 'hello'));
-e syntax OK
C:>perl -MO=Deparse -MWin32 -e"Win32::MsgBox world, 4+MB_ICONQESTION, hello"
use Win32;
Win32::MsgBox('world', 4 + MB_ICONQUESTION(), 'hello');
-e syntax OK
The MB_ICONQUESTION call in the first case is considered a function call with the arguments +4, 'hello'. In the second case, it is considered as a function call with no arguments, and having 4 added to it. It is not a constant, it seems, but a function.
In the source code we get this verified:
sub MB_ICONQUESTION { 0x00000020 }
It is a function that returns 32 (00100000 in binary, indicating a bit being set). Also as Sobrique points out, this is a flag variable, so you should not use addition, but the bitwise logical and/or operators.
In your case, it just accepts any arguments and ignores them. This is a bit confusing if you are expecting a constant.
In your experiment case, the statement
print $/,'results:', join ' ,', T(1), T+1, 1+T
Is interpreted
print $/,'results:', join ' ,', T(1), T(+1, (1+T))
Because execution from right to left goes
1+T = 43
T +1, 43 = 42
T(1) = 42
Because plus + has higher precedence than comma ,, and unary + even higher.
To disambiguate, you need to do use parentheses to clarify precedence:
print $/,'results:', join ' ,', T(1), T()+1, 1+T
# ^^-- parentheses
As a general rule, one should always use parentheses with subroutine calls. In perldoc perlsub there are 4 calling notations:
NAME(LIST); # & is optional with parentheses.
NAME LIST; # Parentheses optional if predeclared/imported.
&NAME(LIST); # Circumvent prototypes.
&NAME; # Makes current #_ visible to called subroutine.
Of which in my opinion, only the first one is transparent, and the other ones a bit obscure.
This is all to do with how you're invoking T and how perl is interpreting the results.
If we deparse your example we get:
BEGIN { $^W = 1; }
sub T {
use strict;
print $/, 'called T(#' . join(',', #_) . '#)';
42;
}
use strict;
print $/, 'results:', join(' ,', T(1), T(1, 1 + T()));
This is clearly not what you've got in mind, but does explain why you get the result you do.
I would suggest in your original example - rather that + you may wish to consider using | as it looks very much like MB_ICONQUESTION is intended to be a flag.
So:
use strict;
use warnings;
use Win32 qw( MB_ICONQUESTION );
print MB_ICONQUESTION;
Win32::MsgBox( "world", 4 | MB_ICONQUESTION , "hello" );
Or
use strict;
use warnings;
use Win32 qw( MB_ICONQUESTION );
print MB_ICONQUESTION;
Win32::MsgBox( "world", MB_ICONQUESTION | 4 , "hello" );
Produce the same result.
This is because of precence when invoking subroutines without brackets - you can do:
print "one", "two";
And both are treated as arguments to print. Perl assumes that arguments after a sub are to be passed to it.
+4 is enumerated as an argument, and passed to T.
sub test { print #_,"\n";};
test 1;
test +1;
If we deparse this, we see perl treats it as:
test 1;
test 1;
So ultimately - there is a bug in Win32 that you have found, that would be fixable by:
sub MB_ICONQUESTION() {0x00000020}
Win32::MsgBox "world", 4 + MB_ICONQUESTION, "hello";
Win32::MsgBox "world", MB_ICONQUESTION + 4, "hello";
Or perhaps:
use constant MB_ICONQUESTION => 0x00000020;
Or as noted - the workaround in your code - don't use + and instead use | which is going to have the same result for bit flag operations, but because of operator precedence is never going to be passed into the subroutine. (Or of course, always specify the parenthesis for your constants)

Anonymous hash in perl

I am starting to learn Perl, so I am trying to read some posts here at SO. Now I came across this code https://stackoverflow.com/a/22310773/2173773 (simplified somewhat here) :
echo "1 2 3 4" | perl -lane'
$h{#F} ||= [];
print $_ for keys %h;
'
What does this code do, and why does this code print 4?
I have tried to study Perl references at http://perldoc.perl.org/perlreftut.html , but I still could not figure this out.
(I am puzzled about this line: $h{#F} ||= [].. )
The -n option (part of -lane) causes Perl to execute the given code for each individual line of input.
The -a option (when used with the -n or -p option) causes Perl to split every line of input on whitespace and store the fields in the #F variable.
$something ||= [] is equivalent to $something = $something || []; i.e., it assigns [] (a reference to an empty array) to the variable $something if & only if $something is already false or undefined.
$h{#F} is an element of the hash %h. Because this expression begins with $ (rather than #), the subscript #F is evaluated in scalar context, and scalar context for an array makes the array evaluate to its length. As the Perl code is only ever executed on the line 1 2 3 4, which is split into four elements, #F will only be four elements long, so $h{#F} is here equivalent to $h{4} (or, technically, $h{"4"}).
Thus, [] will be assigned to $h{"4"}, and as 4 is the only element of the hash %h in existence, keys %h will return a list containing only "4", and printing the elements of this list will print 4.

How does adding to none work?

While trying to reduce some default hash code, I came to discover that you could add to none to produce none or to produce what you're adding to it. Is there a particular reason for this? Will this change on different architectures or can I rely on this ability?
DB<1> print none + 1
DB<2> print 1 + none
1
And just for those that are curious, this is how I'm using it
foreach (#someArray) {
unless ($someHash{$_}++) {
$someHash{$_} = 1;
}
}
as a reduction for
foreach (#someArray) {
if (exists $someHash{$_}) {
$someHash{$_}++;
} else {
$someHash{$_} = 1;
}
}
You are not doing what you think you are doing. These two statements:
print none + 1
print 1 + none
Are not as straightforward as you might think. Because you have warnings turned off, you do not know what they do. Lets try them out in the command prompt, with warnings turned on (-w switch):
$ perl -lwe'print none + 1'
Unquoted string "none" may clash with future reserved word at -e line 1.
Name "main::none" used only once: possible typo at -e line 1.
print() on unopened filehandle none at -e line 1.
$ perl -lwe'print 1 + none'
Unquoted string "none" may clash with future reserved word at -e line 1.
Argument "none" isn't numeric in addition (+) at -e line 1.
1
In the first case, none, which is a bareword, is interpreted as a file handle, and the print statement fails because we never opened a file handle with that name. In the second case, the bareword none is interpreted as a string, which gets converted to a number by the addition operator +, and that number will be zero 0.
You can further clarify this by supplying a specific file handle for the first case:
$ perl -lwe'print STDOUT none + 1'
Unquoted string "none" may clash with future reserved word at -e line 1.
Argument "none" isn't numeric in addition (+) at -e line 1.
1
Which demonstrates that there is no real difference between none + 1 and 1 + none.

Perl go up a character?

In Perl, lets say I have the letter A in variable called $character, and I want it to go up to B, how would I do this? The $character can also be numbers (0-8) and I want the method work on both of them? (Something like binary shift, but not exactly sure if it is something like that). Thanks in advance.
The increment operator may be what you want. However, do make sure that you want the boundary behavior. For instance:
my $character = 'Z';
print ++$character;
Produces:
AA
This is the "with carry" from http://perldoc.perl.org/perlop.html#Auto-increment-and-Auto-decrement.
Simple increment should do what you want:
my $character = "A";
$character++;
from perl-doc:
The auto-increment operator has a little extra builtin magic to it. If
you increment a variable that is numeric, or that has ever been used
in a numeric context, you get a normal increment. If, however, the
variable has been used in only string contexts since it was set, and
has a value that is not the empty string and matches the pattern
/^a-zA-Z*0-9*\z/ , the increment is done as a string, preserving each
character within its range, with carry
Just to add to the other responses:
Note that the magical autoincrement only works if the variable in question has never been used in a numeric context. Thus:
perl -e '$x = "A"; ++$x; print $x' # prints "B"
But:
perl -e '$x = "A"; $x + 0; ++$x; print $x' # prints "1"
To be guaranteed of always getting the magical autoincrement, you should stringify the variable explicitly beforehand:
perl -e '$x = "A"; $x + 0; $x = "$x"; ++$x; print $x' # prints "B"
It may be possible to skip this step if you know the history of the variable you're incrementing and can verify that it has never been used in a numeric context.
Playing with magic can be tricky!