In Expect script, how do you perform simple math and assign the result to a variable? - expect

Learning this language has been difficult because of the very particular syntax required. Everything needs a strange set of braces or brackets or quotes, and I'm pulling out my hair trying to keep it all straight. I can find examples of everything I already know how to do, and I can even find partial examples of what I'm trying to do, but apparently this is such a simple and beginner thing that nobody has bothered to demonstrate it. Either that or I am getting old and I am losing my Google-fu.
I have two variables, thing1 and thing2. I have abandoned my actual code until I figure out how to do 2 + 2, so for now the value of thing1 is 2 and the value of thing2 is 2. I have tried:
set thing_total expr thing1+thing2
set thing_total expr (thing1+thing2)
set thing_total expr [thing1+thing2]
set thing_total expr {thing1+thing2}
set thing_total expr [ thing1+thing2 ]
set thing_total expr [ thing1 + thing2 ]
set thing_total [expr $thing1+$thing2]
set thing_total [expr ($thing1+$thing2)]
set thing_total [expr {$thing1+$thing2}]
set thing_total [expr [ $thing1 + $thing2 ] ]
plus a bunch more that I'm not gonna bother to list. Can someone please tell me how to do math and assign the result to a variable? Don't care about floating point or integer or anything to do with data types.

Thank you, glenn, for getting me on the right track. To get it to work, I had to add spaces:
set thing_total [ expr { $thing1 - $thing2 } ]
Trying to run the code without spaces gave me the same errors:
invalid command name "expr{2+2}"
while executing
"expr{$thing1+$thing2}"
invoked from within
"set thing_total [expr{$thing1+$thing2}]"
(file "./adder.sh" line 11)
Not sure if the spaces need to be inserted between the variables and the brackets or between the variables and the operands, so I just added spaces everywhere.

Related

Bash Subshell Expansion as Parameter to Function

I have a bash function that looks like this:
banner(){
someParam=$1
someOtherParam=$2
precedingParams=2
for i in $(seq 1 $precedingParams);do
shift
done
for i in $(seq 1 $(($(echo ${##}) - $precedingParams)));do
quotedStr=$1
shift
#do some stuff with quotedStr
done
}
This function, while not entirely relevant, will build a banner.
All params, after the initial 2, are quoted lines of text which can contain spaces.
The function fits each quoted string within the bounds of the banner making new lines where it sees fit.
However, each new parameter ensures a new line
My function works great and does what's expected, the problem, however, is in calling the function with dynamic parameters as shown below:
e.g. of call with standard static parameters:
banner 50 true "this is banner text and it will be properly fit within the bounds of the banner" "this is another line of banner text that will be forced to be brought onto a new line"
e.g. of call with dynamic parameter:
banner 50 true "This is the default text in banner" "$([ "$someBool" = "true" ] && echo "Some text that should only show up if bool is true")"
The problem is that if someBool is false, my function will still register the resulting "" as a param and create a new empty line in the banner.
As I'm writing this, I'm finding the solution obvious. I just need to check if -n $quotedStr before continuing in the function.
But, just out of blatant curiosity, why does bash behave this way (what I mean by this is, what is the process through which subshell expansion occurs in relation to parameter isolation to function calls based on quoted strings)
The reason I ask is because I have also tried the following to no avail:
banner 50 true "default str" $([ "$someBool" = "true" ] && echo \"h h h h\")
Thinking it would only bring the quotes down if someBool is true.
Indeed this is what happens, however, it doesn't properly capture the quoted string as one parameter.
Instead the function identifies the following parameters:
default str
"h
h
h
h"
When what I really want is:
default str
h h h h
I have tried so many different iterations of calls, again to no avail:
$([ "$someBool" = "true" ] && echo "h h h h")
$([ "$someBool" = "true" ] && echo \\\"h h h h\\\")
$([ "$someBool" = "true" ] && awk 'BEGIN{printf "%ch h h h h%c",34,34}')
All of which result in similar output as described above, never treating the expansion as a true quoted string parameter.
The reason making the command output quotes and/or escapes doesn't work is that command substitutions (like variable substitutions) treat the result as data, not as shell code, so shell syntax (quotes, escapes, shell operators, redirects, etc) aren't parsed. If it's double-quoted it's not parsed at all, and if it's not in double-quotes, it's subject to word splitting and wildcard expansion.
So double-quotes = no word splitting = no elimination of empty string, and no-double-quotes = word splitting without quote/escape interpretation. (You could do unpleasant things to IFS to semi-disable word splitting, but that's a horrible kluge and can cause other problems.)
Usually, the cleanest way to do things like this is to build a list of conditional arguments (or maybe all arguments) in an array:
bannerlines=("This is the default text in banner") # Parens make this an array
[ "$someBool" = "true" ] &&
bannerlines+=("Some text that should only show up if bool is true")
banner 50 true "${bannerlines[#]}"
The combination of double-quotes and [#] prevents word-splitting, but makes bash expand each array element as a separate item, which is what you want. Note that if the array has zero elements, this'll expand to zero arguments (but be aware that an empty array, like bannerlines=() is different from an array with an empty element, like bannerlines=("")).

Bash error message: syntax error near unexpected token '|'

I was creating a program that calculates the area of circle, but bash doesnt compile and execute due to error message in the title. Here is my code:
elif [ $num -le 6 ] && [ $num -ge 4 ]
then
read -p "Enter radius: " radius
let areaCirc=("scale=2;3.1416 * ($radius * $radius)"|bc)
echo "Area of the circle is: " $areaCirc
and the error message is:
syntax error near unexpected token '|'
can someone help me?
To send a string to a command via stdin, use a here-string command <<< string, not a pipe.
Command substitution syntax is $(...), not (...).
Don't use let here. Shell arithmetic only supports integers.
areaCirc=$(bc <<< "scale=2;3.1416 * ($radius * $radius)")
let provides arithmetic context, but we have an ambiguity here, because in a let expression, the vertical bar (|) means bitwise or, but in the shell it has also the meaning of a pipe operator. Look at the following examples:
let bc=4
let a=(4+bc) # Sets a to 8
let b=("5+bc") # Sets b to 9
let c=("(2+4)|bc")
This is syntactically correct and sets c to 6 (because 2+4 equals 6, and the bitwise or of 6 and 4 equals 6).
However if we decided to quote only part of the argument, i.e.
let c=("(2+4)"|bc)
or don't quote at all, i.e.
let c=((2+4)|bc)
we get a syntax error. The reason is that the shell, when parsing a command, first separates it into the different commands, which then are strung together. The pipe is such an operator, and the shell thinks that you want to do a let areaCirc=("scale=2;3.1416 * ($radius * $radius)" and pipe the result into bc. As you can see, the let statement is uncomplete; hence the syntax error.
Aside from this, even if you would fix it, your using of let would not work, because you are using a fractional number (3.1416), and let can do only integer arithmetic. As a workaround, either you do the whole calculation using bc, or some other language (awk, perl,...), or, if this is an option, you switch from bash to zsh, where you can do floating point arithmetic in the shell.

how to understand the x in ${CONFIG+x}

The code is below,
if [ -z ${CONFIG+x} ]; then
CONFIG=/usr/local/etc/config.ini
else
CONFIG=$(realpath $CONFIG)
fi
Can someone tell me what "x" exactly mean?
It means that if the variable $CONFIG is set, use the value x, otherwise use the null (empty) string.
The test -z checks whether the following argument is empty, so if $CONFIG is not set, the if branch will be taken.
Testing it out (with $a previously unset):
$ echo "a: '${a+x}'"
a: ''
$ a='any other value'
$ echo "a: '${a+x}'"
a: 'x'
This is one of the parameter expansion features defined for the POSIX shell. The simplest is just a variable which is substituted, ${parameter}, while the others have an embedded operator telling what to do with an optional parameter versus an optional word, e.g, "${parameterOPword}"
For this particular case, parameter is CONFIG, OP is + and word is x:
if the parameter is Set and Not Null, the word's value is used, otherwise
if the parameter is Set and Null, the word's value is used, otherwise
a null value is used
A null value for a shell variable is from an explicit assignment, e.g.,
CONFIG=
If a variable has never been assigned to, it is unset. You can make a variable unset using that keyword:
unset CONFIG
There are separate tests for Set and Not Null and Set and Null because the standard lists eight operators. For six of those, the parameter's value would be used if it is Set and Not Null.
In substitution, you cannot see a difference between an unset and null value. Some shells (such as bash) define additional types of parameter expansion; this question is more general.

Change a referenced variable in BASH

I am intending to change a global variable inside a function in BASH, however I don't get a clue about how to do it. This is my code:
CANDIDATES[5]="1 2 3 4 5 6"
random_mutate()
{
a=$1 #assign name of input variable to "a"
insides=${!a} #See input variable value
RNDM_PARAM=`echo $[ 1 + $[ RANDOM % 5 ]]` #change random position in input variable
NEW_PAR=99 #value to substitute
ARR=($insides) #Convert string to array
ARR[$RNDM_PARAM]=$NEW_PAR #Change the random position
NEW_GUY=$( IFS=$' '; echo "${ARR[*]}" ) #Convert array once more to string
echo "$NEW_GUY"
### NOW, How to assign NEW_GUY TO CANDIDATES[5]?
}
random_mutate CANDIDATES[5]
I would like to be able to assign NEW_GUY to the variable referenced by $1 or to another variable that would be pointed by $2 (not incuded in the code). I don't want to do the direct assignation in the code as I intend to use the function for multiple possible inputs (in fact, the assignation NEW_PAR=99 is quite more complicated in my original code as it implies the selection of a number depending the position in a range of random values using an R function, but for the sake of simplicity I included it this way).
Hopefully this is clear enough. Please let me know if you need further information.
Thank you,
Libertad
You can use eval:
eval "$a=\$NEW_GUY"
Be careful and only use it if the value of $a is safe (imagine what happens if $a is set to rm -rf / ; a).

TCL array values updation based on command line argument

I am trying to substitute variable value inside array so as to update array values based on command line inputs.
e.g. I am receiving IP address as command line argument for my TCL script and trying to update commands with recvd IP value.
My array is:
array set myArr { 1 myCmd1("192.268.2.1","abc.txt")
2 myCmd2("192.268.2.1","xyz.txt")
3 myCmd3("192.268.2.1","klm.txt")
}
Here, "192.268.2.1" will actually be supplied as command line argument.
I tried doing
array set myArr { 1 myCmd1($myIP,"abc.txt")
2 myCmd2($myIP,"xyz.txt")
3 myCmd3($myIP,"klm.txt")
}
and other combinations like ${myIP}, {[set $myIP]} but none is working.
Thanks in advance for any help/inputs.
Use the list command
% set myIP 0.0.0.0
0.0.0.0
% array set myArr [list 1 myCmd1($myIP,"abc.txt") 2 myCmd2($myIP,"xyz.txt") 3 myCmd3($myIP,"klm.txt")]
% puts $myArr(1)
myCmd1(0.0.0.0,"abc.txt")
% puts $myArr(3)
myCmd3(0.0.0.0,"klm.txt")
%
I think your code will be easier to understand and easier to maintain if you don't try to use array set in this instance. You can get away with it if you are careful (see answers related to using list) but there's really no reason to do it that way when a more readable solution exists.
Here's my solution:
set myArr(1) "myCmd1($myIP,\"abc.txt\")"
set myArr(2) "myCmd2($myIP,\"xyz.txt\")"
set myArr(3) "myCmd3($myIP,\"klm.txt\")"
try:
array set myArr [list myCmd1($myIP, "abc.txt") 2 myCmd2($myIP, "xyz.txt") ... ]
Why? Because when you write {$var} in Tcl, it means $var and not the contents of the variable var. If you use list to create the list instead of the braces, variables are still evaluated.

Resources