Execute a input in form of mathematical expression in bash - linux

If a user gives the following input in bash. How can I evaluate it using bash?
5+50*3/20 + (19*2)/7
I store it in a variable x. I can't figure out how to proceed.

You can create a simple script for that, let's call test.sh:
#!/bin/bash
x=$1
echo $(($x))
You can replace the line echo $(($x)) with perl -e "print $x" to get floating point output.
And execute it with your input:
$ test.sh "5+50*3/20 + (19*2)/7"
17
With floating point:
#!/bin/bash
x=$1
var=$(perl -e "print $x")
echo $var
The results:
$ test.sh "5+50*3/20 + (19*2)/7"
17.9285714285714
$ test.sh "-105+50*3/20 + (19^2)/7"
-95.0714285714286

You can just pass the expression to calc:
y = $(calc "$x")

Related

double quotation bash scripting linux [duplicate]

This question already has answers here:
What is the difference between $(command) and `command` in shell programming?
(6 answers)
Closed 3 years ago.
I do not understand why the output is the username because in line 3 and 4 must print /usr/bin/whoami.
please explantation simple to me
#!/bin/bash
WHEREWHOAMI="`which whoami`"
ROOTORNOT="`$WHEREWHOAMI`"
echo "$ROOTORNOT"
The variable ROOTORNOT is set to the output of the execution of WHEREWHOAMI which in turn is the output of the command which whoami.
WHEREWHOAMI=`which whoami` # <- /usr/bin/whoami
ROOTWHOAMI="`$WHEREWHOAMI`" # <- `/usr/bin/whoami` # <- username
You can easily figure out what is going on if you add the set -x flag to your script. Example:
$ set -x
$ WHEREWHOAMI="`which whoami`"
++ alias
++ declare -f
++ /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot whoami
+ WHEREWHOAMI=/usr/bin/whoami
$ ROOTORNOT="`$WHEREWHOAMI`"
++ /usr/bin/whoami
+ ROOTORNOT=kvantour
$ echo "$ROOTORNOT"
+ echo kvantour
kvantour
$
Backticks are evaluated even inside double quotes.
(Suggestion - don't use backticks. use $() instead.)
WHEREWHOAMI="`which whoami`"
This executes which whoami and assigns /usr/bin/whoami to WHEREWHOAMI.
ROOTORNOT="`$WHEREWHOAMI`"
This executes /usr/bin/whoami in backticks, and assigns the USERNAME result to ROOTORNOT.
It's doing exactly what it should.
Is that not what you indended?
Perhaps what you wanted was something like -
$: [[ $( $(which whoami) ) == root ]] && echo ROOT || echo not-root
not-root
Though I do suggest storing the value and comparing that.
Is there a reason you can't just use
if [[ root == "$LOGNAME" ]]
then : ...
?

Define bash variable to be evaluated every time it is used

I want to define bash a variable which will be evaluated every time it is used.
My goal is to define two variables:
A=/home/userA
B=$A/my_file
So whenever I update A, B will be updated with the new value of A
I know how to do it in prompt variables, but, is there a way to do it for regular variables?
If you have Bash 4.4 or newer, you could (ab)use the ${parameter#P} parameter expansion, which expands parameter as if it were a prompt string:
$ A='/home/userA'
$ B='$A/my_file' # Single quotes to suppress expansion
$ echo "${B#P}"
/home/userA/my_file
$ A='/other/path'
$ echo "${B#P}"
/other/path/my_file
However, as pointed out in the comments, it's much simpler and more portable to use a function instead:
$ appendfile() { printf '%s/%s\n' "$1" 'my_file'; }
$ A='/home/user'
$ B=$(appendfile "$A")
$ echo "$B"
/home/user/my_file
$ A='/other/path'
$ B=$(appendfile "$A")
$ echo "$B"
/other/path/my_file
No. Use a simple and robust function instead:
b() {
echo "$a/my_file"
}
a="/home/userA"
echo "b outputs $(b)"
a="/foo/bar"
echo "b outputs $(b)"
Result:
b outputs /home/userA/my_file
b outputs /foo/bar/my_file
That said, here's one ugly way of fighting the system accomplish your goal verbatim:
# Trigger a re-assignment after every single command
trap 'b="$a/my_file"' DEBUG
a="/home/userA"
echo "b is $b"
a="/foo/bar"
echo "b is $b"
Result:
b is /home/userA/my_file
b is /foo/bar/my_file

bash truncate string Works on commandline but not in script

I am trying to cut off all occurrences of a character from the end of a string.
The test script I came up with is:
#!/bin/bash
Input="/tmp/blah/bloh/////////////"
Desired="/tmp/blah/bloh"
cut='/'
result=${Input%%+(${cut})}
echo " Input: ${Input}"
echo "Expected result: ${Desired}"
echo " Result: ${result}"
echo "---------------------------------------"
echo -n " Outcome: "
[ "${Desired}" = "${result}" ] && echo "Success!" || echo "Fail!"
Running this script via bash /tmp/test.sh gives the following output:
Input: /tmp/blah/bloh/////////////
Expected result: /tmp/blah/bloh
Result: /tmp/blah/bloh/////////////
---------------------------------------
Outcome: Fail!
However, if I copy and paste the entire thing in my console I get the expected result of /tmp/blah/blah
What is going on here?
+(${cut}) is an extended pattern, which bash does not recognize by default. You need to enable the extglob option first.
$ shopt -s extglob
$ Input="/tmp/blah/bloh/////////////"
$ cut='/'
$ echo "${Input%%+(${cut})}"
/tmp/blah/bloh
You probably have extglob enabled in your interactive shell via your .bashrc or .bash_profile configuration file, but neither file is used for the non-interactive shell started by your script.

Bash: highlight command before execution (set -x)

I have a bash script which executes about 20 commands and for debugging purposes I find myself scrolling through the output a lot.
Unfortunately bash doesn't tell me which part of the output is part of what command.
When I use "set -x" in the script it at least prints some information on what it just executed, but I don't really like the output it generates.
For instance, if I have this script:
#!/bin/bash
set -x
echo "foo"
if [ "$ASD" == "QWE" ] ; then
echo "bar"
fi
I would like the output to be something like this:
echo "foo"
foo
echo "bar"
bar
Or maybe:
echo "foo"
foo
if [ "value_of_ASD" == "QWE" ] ; then
echo "bar"
bar
fi
Instead of printing the commands in bold, highlighting with a color would also be okay. But I don't just want to have "+" characters in front of the commands and I also don't like the if statements showing up like '[' value_of_ASD == QWE ']'.
How can I accomplish that with bash?
At the moment the output looks like this btw:
+ echo foo
foo
+ '[' value_of_ASD == QWE ']'
+ echo bar
bar
Edit:
One idea I had was to write a script that I would source in the very beginning of the main script and then let the sourced script parse the main one. Something like this:
source_me.sh
#!/bin/bash
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/$(basename $0)"
FORMAT_SET_BOLD='\033[0;1m'
FORMAT_RESET='\033[0m'
cat $SCRIPT_PATH | tail -n "+$START_LINE" | while read line; do
printf "${FORMAT_SET_BOLD}${line}${FORMAT_RESET}\n"
eval "${line}"
done
exit 0;
main.sh
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
echo "Bar"
echo "+Hello"
The output in that case is:
echo "Foo"
Foo
echo "Bar"
Bar
echo "+Hello"
+Hello
But this method will fail if I use more complex code that goes over multiple lines (if statements, loops etc):
#!/bin/bash
START_LINE=$((LINENO+1)) source ./source_me.sh
echo "Foo"
if [ "$FOOBAR" == "" ] ; then
echo "Bar"
fi
echo "+Hello"
In this case I get:
echo "Foo"
Foo
if [ "$FOOBAR" == "" ] ; then
./source_me.sh: eval: line 9: syntax error: unexpected end of file
echo "Bar"
Bar
fi
./source_me.sh: eval: line 8: syntax error near unexpected token ´fi'
./source_me.sh: eval: line 8: ´fi'
echo "+Hello"
+Hello
I would like to extend rubo77's answer with a few examples that I think deserve a separate answer:
Plain text prefix
So the basic example is to set PS4 to some plain text, e.g.:
PS4="# "; set -x
Which will result in:
Color & extra line text prefix
But because you can use special characters and ANSI escape codes you can for example add a new line before each new command and print the prefix in a color, e.g.:
PS4="\n\033[1;33m>>>\033[0m "; set -x
Result:
Dynamic color prefix
Finally you can make the command prefix call other programs with each use, which you can use to add a timestamp, e.g.:
# yes, there have to be single quotes below, not double!
PS4='\033[1;34m$(date +%H:%M:%S)\033[0m '; set -x
Result:
You can change the + to a string of your desire by setting PS4, e.g.:
PS4="# "; set -x
Note that set -x has no innate knowledge of your terminal, which would be required to send the correct characters to create bold or coloured text.
If you can capture all output through a single file handle, you can probably do this with a pipe:
$ /path/to/your/script 2>&1 | sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/"
You might want to man tput for more information about the tool we're using to get bold type.
The viability of this depends on many factors I don't know about your environment. YMMV, may contain nuts.
Note:
You might think it was better to capture stderr separately with a line like this:
$ /path/to/your/script 2> >(sed "/^+ /s/.*/$(tput bold)&$(tput sgr0)/")
Alas, this doesn't behave consistently, because stderr passing through the pipe may not be submitted to the terminal before subsequent stdout from the script itself. Note also that this solution, because it uses process substitution, is a bashism and is not portable to POSIX.
What you are looking for is set -v.
-v prints out the line the interpreter just read.
-x prints out the post-parsing results of the line that the interpreter just read.
file x:
set -vx
foo=1
echo $foo
set +vx
Executing:
$: . x
foo=1
++ foo=1
echo $foo
++ echo 1
1
set +vx
++ set +vx

"printf -v" inside function not working with redirected output

With bash 4.1.2 and 4.3.48, the following script gives the expected output:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult
echo ${theResult}
echo Done.
Output as expected:
$ ./returnSimple
Hello World
ERROR
Done.
However, when stdout from the function is piped to another process, the assignment of the __resultvar variable does not work anymore:
#!/bin/bash
returnSimple() {
local __resultvar=$1
printf -v "$__resultvar" '%s' "ERROR"
echo "Hello World"
}
returnSimple theResult | cat
echo ${theResult}
echo Done.
Unexpected Output:
$ ./returnSimple
Hello World
Done.
Why does printf -v not work in the second case? Should printf -v not write the value into the result variable independent of whether the output of the function is piped to another process?
See man bash, section on Pipelines:
Each command in a pipeline is executed as a separate process (i.e., in a subshell).
That's why when you write cmd | cat, cmd receives a copy of variable that it can't modify.
A simple demo:
$ test() ((a++))
$ echo $a
$ test
$ echo $a
1
$ test | cat
$ echo $a
1
Interestingly enough, the same also happens when using eval $__resultvar="'ERROR'" instead of the printf -v statement. Thus, this is not a printf related issue.
Instead, adding a echo $BASH_SUBSHELL to both the main script and the function shows that the shell spawns a sub shell in the second case - since it needs to pipe the output from the function to another process. Hence the function runs in a sub shell:
#!/bin/bash
returnSimple() {
local __resultvar=$1
echo "Sub shell level: $BASH_SUBSHELL"
printf -v "$__resultvar" '%s' "ERROR"
}
echo "Sub shell level: $BASH_SUBSHELL"
returnSimple theResult | cat
echo ${theResult}
echo Done.
Output:
% ./returnSimple.sh
Sub shell level: 0
Sub shell level: 1
Done.
This is the reason why any variable assignments from within the function are not passed back to the calling script.

Resources