How to evaluate mathematics in Ubuntu console? [duplicate] - bash

This question already has answers here:
Bash: evaluate a mathematical term?
(9 answers)
Closed 6 years ago.
When I try to calculate a maths problem such as 1 + 1 using the Ubuntu console like this:
user#servername:~$ 1 + 1
Ubuntu thinks the first 1 is a command and I get this error:
1: command not found
I then tried to use echo to evaluate the string (with and without quotes) like this:
user#servername:~$ echo 1 + 1
user#servername:~$ echo "1 + 1"
Unfortunatly both output 1 + 1 and not 2.
It would also be greatly appreciated to include a explanation as to why echo does not evaluate the specified string before outputting it?
Also is there is a built-in command that evaluates a string before outputting it (maybe something that behaves like eval in Python)?
Thank you for the help in advance.

The one I usually use is
bc<<<2+2
You can make that easier to type with an alias:
$ alias x='bc<<<'
$ x 53*(27-23)
212
If you put whitespace in the expression you'll need to quote it. Also if the expression starts with an open parenthesis (as opposed to having one in the middle).

$ echo $((1+1))
2
$ echo "1+1" | bc
2
$ awk 'BEGIN{print 1+1}'
2

Related

Unix script error while assigning command substitution to array variable [duplicate]

This question already has answers here:
Why does my Bash code fail when I run it with 'sh'?
(2 answers)
Closed 2 years ago.
From the below script I am assigning the command substitution for the array variables.
When I execute the script, the script is throwing error as
myscript.sh: 22: scr4.sh: Syntax error: "(" unexpected
ie while executing the command suite=( echo $Suites ).
I took this script from Advanced Bash scripting Guide pdf.
When I run this command by command in CLI, there is no issue.
What is the solution for this?
#!/bin/bash
Suites="Clubs
Diamonds
Hearts
Spades"
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
suite=( `echo $Suites` )
denomination=( `echo $Denominations` )
num_suites=${#suite[*]}
num_denominations=${#denomination[*]}
echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}
exit 0
You're probably using the wrong interpreter.
$ bash cards.sh
King of Diamonds
$ sh cards.sh
cards.sh: 22: Syntax error: "(" unexpected
POSIX shell does not support arrays. You define an array on line 22 (suite=( `echo $Suites` )) and another on the next code line, then most of your remaining lines refer to those arrays. $RANDOM is also unavailable in POSIX.
Another note, you don't need those subshells. Here's a cleaned up version of your code:
#!/bin/bash
suit=( Clubs Diamonds Hearts Spades )
denomination=( {2..10} Jack Queen King Ace )
echo -n "${denomination[ $(( RANDOM % ${#denomination[#]} )) ]} of "
echo "${suit[ $(( RANDOM % ${#suit[#]} )) ]}"
exit 0
This doesn't launch any subshells. Instead, it's directly defining the arrays. I used {2..10} (another bashism) as shorthand for listing each number. Rather than storing the lengths in variables, I'm invoking them directly in the modulo math, which I've spaced out to make more legible.
I changed ${variable[*]} to ${variable[#]} as well. This doesn't matter in your particular code, but it's generally better to use # since it is better at preserving spacing.
I also corrected the spelling of your suite variable to suit. Feel free to revert if that's actually what you desired (perhaps due to a spoken language difference). It of course doesn't really matter as long as you're consistent; bash doesn't know English 😉

Comparisons in Shell [duplicate]

This question already has answers here:
Why should there be spaces around '[' and ']' in Bash?
(5 answers)
Closed 4 years ago.
I am trying to write a program that checks whether the number stored in a file (variable n) + 8 is greater or equal to 100. If it is, terminate, else, add 8 and store back in file. However, when I try running it, it says the command in line 4 (if condition) cannot be found. Can someone please explain to me why this isn't working? Thanks.
#!/bin/bash
n=$(cat test.txt)
if [$(($n+8)) -ge 100]
then
echo 'terminated program' > test.txt
else
m=$(($n+3))
echo $m > test.txt
fi
You miss some spaces :
if [$(($n+8)) -ge 100]
->
if [ $(($n+8)) -ge 100 ]
But while using bash, prefer a modern solution, using bash arithmetic :
if (( n+8 >= 100 ))
or even
if ((n+8>=100))
Like #Gordon Davisson said in comments : arithmetic contexts like inside (( )) are one of the few places in bash where spaces aren't critical delimiters.

Read range of numbers into a for loop

So, I am building a bash script which iterates through folders named by numbers from 1 to 9. The script depends on getting the folder names by user input. My intention is to use a for loop using read input to get a folder name or a range of folder names and then do some stuff.
Example:
Let's assume I want to make a backup with rsync -a of a certain range of folders. Usually I would do:
for p in {1..7}; do
rsync -a $p/* backup.$p
done
The above would recursively backup all content in the directories 1 2 3 4 5 6 and 7 and put them into folders named as 'backup.{index-number}'. It wouldn't catch folders/files with a leading . but that is not important right now.
Now I have a similar loop in an interactive bash script. I am using select and case statements for this task. One of the options in case is this loop and it shall somehow get a range of numbers from user input. This now becomes a problem.
Problem:
If I use read to get the range then it fails when using {1..7} as input. The input is taken literally and the output is just:
{1..7}
I really would like to know why this happens. Let me use a more descriptive example with a simple echo command.
var={1..7} # fails and just outputs {1..7}
for p in $var; do echo $p;done
read var # Same result as above. Just outputs {1..7}
for p in $var; do echo $p;done
for p in {1..7}; do echo $p;done # works fine and outputs the numbers 1-7 seperated with a newline.
I've found a workaround by storing the numbers in an array. The user can then input folder names seperated by a space character like this: 1 2 3 4 5 6 7
read -a var # In this case the output is similar to the 3rd loop above
for p in ${var[#]}; do echo $p; done
This could be a way to go but when backing up 40 folders ranging from 1-40 then adding all the numbers one-by-one completely makes my script redundant. One could find a solution to one of the millennium problems in the same time.
Is there any way to read a range of numbers like {1..9} or could there be another way to get input from terminal into the script so I can iterate through the range within a for-loop?
This sounds like a question for google but I am obviously using the wrong patterns to get a useful answer. Most of similar looking issues on SO refer to brace and parameter expansion issues but this is not exactly the problem I have. However, to me it feels like the answer to this problem is going in a similar direction. I fail to understand why when a for-loop for assigning {1..7} to a variable works but doing the same like var={1..7} doesn't. Plz help -.-
EDIT: My bash version:
$ echo $BASH_VERSION
4.2.25(1)-release
EDIT2: The versatility of a brace expansion is very important to me. A possible solution should include the ability to define as many ranges as possible. Like I would like to be able to choose between backing up just 1 folder or a fixed range between f.ex 4-22 and even multiple options like folders 1,2,5,6-7
Brace expansion is not performed on the right-hand side of a variable, or on parameter expansion. Use a C-style for loop, with the user inputing the upper end of the range if necessary.
read upper
for ((i=1; i<=$upper; i++)); do
To input both a lower and upper bound separated by whitespace
read lower upper
for (i=$lower; i <= $upper; i++)); do
For an arbitrary set of values, just push the burden to the user to generate the appropriate list; don't try to implement your own parser to process something like 1,2,20-22:
while read p; do
rsync -a $p/* backup.$p
done
The input is one value per line, such as
1
2
20
21
22
Even if the user is using the shell, they can call your script with something like
printf '%s\n' 1 2 20..22 | backup.sh
It's easier for the user to generate the list than it is for you to safely parse a string describing the list.
The evil eval
$ var={1..7}
$ for i in $(eval echo $var); do echo $i; done
this also works,
$ var="1 2 {5..9}"
$ for i in $(eval echo $var); do echo $i; done
1
2
5
6
7
8
9
evil eval was a joke, that is, as long as you know what you're evaluating.
Or, with awk
$ echo "1 2 5-9 22-25" |
awk -v RS=' ' '/-/{split($0,a,"-"); while(a[1]<=a[2]) print a[1]++; next}1'
1
2
5
6
7
8
9
22
23
24
25

for loops with variables in range won't work

So I was writing a for loop and getting some errors, to get an understanding of the errors I wrote this
#! /bin/bash
b=${1:- 10}
echo $b
for i in {0..$b}
do
echo "$i"
done
so if I run ./forloop.sh 10
I get
10
{0..10}
why doesn't the range work when I have a variable as the second argument?
Bash doesn't expand the range. Use this instead.
for (( i=0; i<=$b; i++))
The part of bash that expands things like {1..10} into 1 2 3 4 5 6 7 8 9 10 runs before any parameters like $b are replaced by their values. Since {1..$b} doesn't look like a numeric range, it doesn't get expanded. By the time parameter expansion turns it into {1..10}, it's too late; nothing is going to come along and evaluate the curly-brace expression.
Change the script to use the following (http://ideone.com/MwAi16).
b=10
for i in $(eval echo {0..$b})

Is it possible to build variable names from other variables in bash? [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed 6 years ago.
I apologise for the pretty terrible title - and the poor quality post - but what I basically want to do is this:
for I in 1 2 3 4
echo $VAR$I # echo the contents of $VAR1, $VAR2, $VAR3, etc.
Obviously the above does not work - it will (I think) try and echo the variable called $VAR$I Is this possible in Bash?
Yes, but don't do that. Use an array instead.
If you still insist on doing it that way...
$ foo1=123
$ bar=foo1
$ echo "${!bar}"
123
for I in 1 2 3 4 5; do
TMP="VAR$I"
echo ${!TMP}
done
I have a general rule that if I need indirect access to variables (ditto arrays), then it is time to convert the script from shell into Perl/Python/etc. Advanced coding in shell though possible quickly becomes a mess.
You should think about using bash arrays for this sort of work:
pax> set arr=(9 8 7 6)
pax> set idx=2
pax> echo ${arr[!idx]}
7
For the case where you don't want to refactor your variables into arrays...
One way...
$ for I in 1 2 3 4; do TEMP=VAR$I ; echo ${!TEMP} ; done
Another way...
$ for I in 1 2 3 4; do eval echo \$$(eval echo VAR$I) ; done
I haven't found a simpler way that works. For example, this does not work...
$ for I in 1 2 3 4; do echo ${!VAR$I} ; done
bash: ${!VAR$I}: bad substitution
for I in {1..5}; do
echo $((VAR$I))
done
Yes. See Advanced Bash-Scripting Guide
This definately looks like an array type of situation. Here's a link that has a very nice discussion (with many examples) of how to use arrays in bash: Arrays

Resources