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

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.

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=("")).

Escape brackets in input

I'm trying to write a quick script to pass simple calculations through to bc, however when I try a calculation involving brackets I get the following error:
-bash: syntax error near unexpected token `('
How do I write the script to escape any characters given to it?
This is the code I have now:
calc()
{
echo "$*" | bc
}
And I call it with things like:
calc 100 + 10 + (10 * 10)
Quoting the input works but I'd like to not have to (I'm clearly lazy enough to be trying to make adding numbers easier).
calc '100 + 10 + (10 * 10)'
calc 100 + 10 + '(10 * 10)'
calc 100 + 10 + \(10 \* 10\)
The error is not in calc() but in the code calling it. Parentheses and asterisks are special characters in the shell so you need to quote them. There's nothing you can do within calc() since the shell's barfing before it even calls it.
Quoting the input works but I'd like to not have to.
It's unavoidable. Sorry!

Ruby command line script: Trying to pass variable in switch case

I'm creating a ruby command line tool which has a switch case statement, I'd like to pass through variables on this switch case statement for example:
input = gets.chomp
case input
when 'help'
display_help
when 'locate x, y' # this is the bit i'm stuck on
find_location(x, y)
when 'disappear s'
disappear_timer(s)
when 'exit'
exit
else
puts "incorrect input"
end
Essentially I want the user to be able to type in locate 54, 30 or sleep 5000 and then call a function which handles the number they passed. I was wondering how I can either pass arguments from the user in a switch statement like this for my command line tool like this?
Use Regexp matcher inside when:
when /locate \d+, \d+/
find_location *input.scan(/\d+/).map(&:to_i)
Here we basically match whatever is locate followed by digits, comma, space, digits. If matched, we extract the digits from the string with String#scan and then convert to Integers, finally passing them as an argument to find_location method.

Comparing integers and decimals in bash [duplicate]

This question already has answers here:
How can I compare two floating point numbers in Bash?
(22 answers)
Closed 7 years ago.
I am trying to compare two numbers stored in variables in a shell script. The objective is to find if the incoming value is inside the specified ranges: 500-600 or 700-800.
My current code looks like:
inc=0
srange[0]=500
erange[0]=600
srange[1]=700
erange[1]=800
for i in "${startrange[#]}"; do
if [[ " ${startrange[$inc]} " -lt " $actual " ]]; then
echo " in range "
fi
inc = $((inc+1))
done
The code works with integer values such as 530 or 540 but fails with decimal values like 530.2 or 540.3, resulting in a syntax error on this line of the code:
if (( " ${startrange[$inc]} " -lt " $actual " )); then
How can I fix this to handle the decimal values?
The shell can only do integer calculations. If you want to compare values that are non-integers, you must use a tool that can understand decimal math. bc is one such option:
if bc <<< "${startrange[inc]} < $actual"; then …
It seems like you're saying that your code works for integer numbers but not for decimal numbers.
In that case you could simply turn all your numbers into integers before you do the comparison to avoid that error.
This is one way to turn decimals to integers in bash:
DECIMAL=3.14
INTEGER=${DECIMAL%.*}
Note that if you used this code to try to turn an integer into an integer it would return the same number so you can apply this to any number you get regardless of whether or not it is a decimal.

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).

Resources