passing bash expression to AWK for floating point arithmetic - linux

I was trying to do floating point arithmetic in bash, but as floats are not supported, was trying to get the solution using AWK. Below is the issue i am facing:
I see this working fine for me:
code
echo - | awk '{printf("%04.4f \n", (-225.2*7+30*6)/17 + (19^2)/9) }'
output
-42.0301
But my motive is to "read an expression" and compute value correct to 4 decimals, so tried below code inputting same expression (-225.2*7+30*6)/17 + (19^2)/9) and its giving incorrect values(i guess variable is passed as string to awk):
code
read inpt
echo - | awk -v input=$inpt '{printf("%04.4f \n", input) }'
output
0.0000
Note: Please ignore the space around second + in this example expression, that i can remove using sed or similar methods(with space i get syntactical error in awk while passing variable from bash).
Any help is highly appreciated. Thanks in advance
PS: the bash version in my case is "bash-4.2". I guess its the version of bash preventing me using from many other options.

You can't evaluate data in a variable in awk out of the box. In this case you need to write an arithmetic evaluator or use a pre-existing one, like https://github.com/radare/radare2-bindings/blob/master/awk/calc.awk . Once you fix that missing parenthesis and quote your expression properly, you can:
$ echo "((-225.2*7+30*6)/17 + (19^2)/9)" | awk -f calc.awk
((-225.2*7+30*6)/17 + (19^2)/9) = -42.0301

I recommend and have upvoted James Brown's answer. But if you need something here and now with no external dependencies, you can simply interpolate the input into the script.
awk "END { printf("%04.4f\n", $input) }" </dev/null
This is functionally equivalent to using eval so if this isn't deployed where you know you can trust the input (e.g. because it comes from a controlled process, not an actual user) you will need to perform some sort of sanitization (and even then probably cope with odd or outright misleading error messages if the input isn't a well-formed Awk expression).
read -p "Input an Awk expression: " input
case $input in
*[!-+/*()^%0-9]*)
echo "$0: invalid input" >&2
exit 1;;
esac
awk ...
Notice also the construct to avoid the basically useless echo. Redirecting input from /dev/null and putting your code in the END (or BEGIN) block is a standard technique for running an arbitrary piece of Awk script without requiring any input.

First, I don't see anything wrong in the command, it should work. Please try again the exact commands posted and provide exact details if the issue persists.
However, if all you need is formating the output, you can do it directly with printf.
$ read input
12.34
$ printf '%4.4f' $input
12.3400
EDIT:
If you need to format the output after performing some calculation, then you can alternatively use bc. (awk should still work)
$ echo "scale=4; (-225.2*7+30*6)/17+(19^2)/9" | bc
-42.0300
You can use variables in the expression as usual,
$ read inpt
1234
$ echo "scale=4; $inpt * 0.01" | bc
12.34

Is this what you are looking for ?
echo "25 50"| awk '{print $2,"/",$1}' | bc

Related

More convenient way to do arithmetic with program output at the shell?

I usually need to run programs to do some file checking, like say use wc to count the lines of a file and then do some arithmetic with it. Usually the way I do this is just getting the output and then doing the arithmetic by opening a python terminal or whichever software can be used to do so.
If I have to do it many times, then this gets a bit annoying, and I'd like to have some method to take the output directly and do the arithmetic that I want. For instance, one that I like is using perl in the following way, assuming I have to take the output of wc and divide it by 12:
perl -e 'print `wc -l file`/12'
This can be useful but gets annoying after a while. Since this is probably something people need to do all the time, I'd like to know what better faster methods people use to do this fast. I've seen expr might be even better, but I get a syntax error when passing it the output of something bound in ``, like above. So basically the shortest, most efficient way one can do this simple arithmetic in linux terminals from file outputs.
Double parentheses ((...)) perform arithmetic, and with a dollar sign $((...)) you can get the result as a string.
echo $((`wc -l < file` / 12))
echo $(($(wc -l < file) / 12))
You can use variables and they don't need dollar signs. Both var and $var are acceptable:
lines=$(wc -l < file)
echo $((lines / 12))
if ((lines * 42 + 17 > 630)); then
...
fi
So basically I have tested with a code on my bash:
Multiline code:
a=$(echo "hi" | wc -l)
echo $a
b=`expr $a + 2`
echo $b
Which I have changed to one line:
echo `expr $(echo "hi" | wc -l) + 20`
echo "hi" | wc -l is calculating no of lines and is within $() which makes it as one variable and evaluate its value
Then expr takes two arguements here and make sure you to use space before and after the operator and use a backtic(`) to evaluate thi and doing echo finally

sed not working on a variable within a bash script; requesting a file. Simple example

If I declare a variable within a bash script, and then try to operate on it with sed, I keep getting errors. I've tried with double quotes, back ticks and avoiding single quotes on my variable. Here is what I'm essentially doing.
Call my script with multiple parameters
./myScript.sh apples oranges ilike,apples,oranges,bananas
My objective is to use sed to replace $3 "," with " ", then use wc -w to count how many words are in $3.
MyScript.sh
fruits="$3"
checkFruits= sed -i 's/,/ /g' <<< "$fruits"
echo $checkFruits
And the result after running the script in the terminal:
ilike,apples,oranges,bananas
sed: no input files
P.s. After countless google searches, reading suggestions and playing with my code, I simply cannot get this easy sample of code to work and I'm not really sure why. And I can't try to implement the wc -w until I move past this block.
You can do
fruits="$3"
checkFruits="${3//,/ }"
# or
echo "${3//,/ }"
The -i flag to sed requires a file argument, without it the sed command does what you expect.
However, I'd consider using tr instead of sed for this simple replacement:
fruits="$3"
checkFruits="$(tr , ' ' <<< $fruits)"
echo $checkFruits
Looking at the larger picture, do you want to count comma-separated strings, or the number of words once you have changed commas into spaces? For instance, do you want the string "i like,apples,oranges,and bananas" to return a count of 4, or 6? (This question is moot if you are 100% sure you will never have spaces in your input data.)
If 6, then the other answers (including mine) will already work.
However, if you want the answer to be 4, then you might want to do something else, like:
fruits="$3"
checkFruits="$(tr , \\n <<< $fruits)"
itemCount="$(wc -l <<< $checkFruits)"
Of course this can be condensed a little, but just throwing out the question as to what you're really doing. When asking a question here, it's good to post your expected results along with the input data and the code you've already used to try to solve the problem.
The -i option is for inplace editing of input file, you don't need it here.
To assign a command's output to a variable, use command expansion like var=$(command).
fruits="$3"
checkFruits=$(sed 's/,/ /g' <<< "$fruits")
echo $checkFruits
You don't need sed at all.
IFS=, read -a things <<< "$3"
echo "${#things[#]}"

How can I replace 'bc' tool in my bash script?

I have the following command in my bash script:
printf '\n"runtime": %s' "$(bc -l <<<"($a - $b)")"
I need to run this script on around 100 servers and I have found that on few of them bc is not installed. I am not admin and cannot install bc on missing servers.
In that case, what alternative can i use to perform the same calculation? Please let me know how the new command should look like
In case you need a solution which works for floating-point arithmetic you can always fall back to Awk.
awk -v a="$a" -v b="$b" 'BEGIN { printf "\n\"runtime\": %s", a-b }' </dev/null
Putting the code in a BEGIN block and redirecting input from /dev/null is a common workaround for when you want to use Awk but don't have a file of lines to loop over, which is what it's really designed to do.
If you are only dealing with integers you can use bash's arithmetic expansion for this:
printf '\n"runtime": %s' $((a - b))
Note that this does assume you have bash available (as you've indicated you do). If you only have a stripped down Bourne shell (/bin/sh) arithmetic expansion is not available to you.

Get first character of a string SHELL

I want to first the first character of a string, for example:
$>./first $foreignKey
And I want to get "$"
I googled it and I found some solutions but it concerns only bash and not Sh !
This should work on any Posix compatible shell (including sh). printf is not required to be a builtin but it often is, so this may save a fork or two:
first_letter=$(printf %.1s "$1")
Note: (Possibly I should have explained this six years ago when I wrote this brief answer.) It might be tempting to write %c instead of %.1s; that produces exactly the same result except in the case where the argument "$1" is empty. printf %c "" actually produces a NUL byte, which is not a valid character in a Posix shell; different shells might treat this case differently. Some will allow NULs as an extension; others, like bash, ignore the NUL but generate an error message to tell you it has happened. The precise semantics of %.1s is "at most 1 character at the start of the argument, which means that first_letter is guaranteed to be set to the empty string if the argument is the empty string, without raising any error indication.
Well, you'll probably need to escape that particular value to prevent it being interpreted as a shell variable but, if you don't have access to the nifty bash substring facility, you can still use something like:
name=paxdiablo
firstchar=`echo $name | cut -c1-1`
If you do have bash (it's available on most Linux distros and, even if your login shell is not bash, you should be able to run scripts with it), it's the much easier:
firstchar=${name:0:1}
For escaping the value so that it's not interpreted by the shell, you need to use:
./first \$foreignKey
and the following first script shows how to get it:
letter=`echo $1 | cut -c1-1`
echo ".$letter."
Maybe it is an old question.
recently I got the same problem, according to POSIX shell manual about substring processing, this is my solution without involving any subshell/fork
a="some string here"
printf 'first char is "%s"\n' "${a%"${a#?}"}"
for shell sh
echo "hello" | cut -b 1 # -b 1 extract the 1st byte
h
echo "hello" |grep -o "." | head -n 1
h
echo "hello" | awk -F "" '{print $1}'
h
you can try this for bash:
s='hello'; echo ${s:0:1}
h
printf -v first_character "%c" "${variable}"

How to pass the value of a variable to the standard input of a command?

I'm writing a shell script that should be somewhat secure, i.e., does not pass secure data through parameters of commands and preferably does not use temporary files. How can I pass a variable to the standard input of a command?
Or, if it's not possible, how can I correctly use temporary files for such a task?
Passing a value to standard input in Bash is as simple as:
your-command <<< "$your_variable"
Always make sure you put quotes around variable expressions!
Be cautious, that this will probably work only in bash and will not work in sh.
Simple, but error-prone: using echo
Something as simple as this will do the trick:
echo "$blah" | my_cmd
Do note that this may not work correctly if $blah contains -n, -e, -E etc; or if it contains backslashes (bash's copy of echo preserves literal backslashes in absence of -e by default, but will treat them as escape sequences and replace them with corresponding characters even without -e if optional XSI extensions are enabled).
More sophisticated approach: using printf
printf '%s\n' "$blah" | my_cmd
This does not have the disadvantages listed above: all possible C strings (strings not containing NULs) are printed unchanged.
(cat <<END
$passwd
END
) | command
The cat is not really needed, but it helps to structure the code better and allows you to use more commands in parentheses as input to your command.
Note that the 'echo "$var" | command operations mean that standard input is limited to the line(s) echoed. If you also want the terminal to be connected, then you'll need to be fancier:
{ echo "$var"; cat - ; } | command
( echo "$var"; cat - ) | command
This means that the first line(s) will be the contents of $var but the rest will come from cat reading its standard input. If the command does not do anything too fancy (try to turn on command line editing, or run like vim does) then it will be fine. Otherwise, you need to get really fancy - I think expect or one of its derivatives is likely to be appropriate.
The command line notations are practically identical - but the second semi-colon is necessary with the braces whereas it is not with parentheses.
This robust and portable way has already appeared in comments. It should be a standalone answer.
printf '%s' "$var" | my_cmd
or
printf '%s\n' "$var" | my_cmd
Notes:
It's better than echo, reasons are here: Why is printf better than echo?
printf "$var" is wrong. The first argument is format where various sequences like %s or \n are interpreted. To pass the variable right, it must not be interpreted as format.
Usually variables don't contain trailing newlines. The former command (with %s) passes the variable as it is. However tools that work with text may ignore or complain about an incomplete line (see Why should text files end with a newline?). So you may want the latter command (with %s\n) which appends a newline character to the content of the variable. Non-obvious facts:
Here string in Bash (<<<"$var" my_cmd) does append a newline.
Any method that appends a newline results in non-empty stdin of my_cmd, even if the variable is empty or undefined.
I liked Martin's answer, but it has some problems depending on what is in the variable. This
your-command <<< """$your_variable"""
is better if you variable contains " or !.
As per Martin's answer, there is a Bash feature called Here Strings (which itself is a variant of the more widely supported Here Documents feature):
3.6.7 Here Strings
A variant of here documents, the format is:
<<< word
The word is expanded and supplied to the command on its standard
input.
Note that Here Strings would appear to be Bash-only, so, for improved portability, you'd probably be better off with the original Here Documents feature, as per PoltoS's answer:
( cat <<EOF
$variable
EOF
) | cmd
Or, a simpler variant of the above:
(cmd <<EOF
$variable
EOF
)
You can omit ( and ), unless you want to have this redirected further into other commands.
Try this:
echo "$variable" | command
If you came here from a duplicate, you are probably a beginner who tried to do something like
"$variable" >file
or
"$variable" | wc -l
where you obviously meant something like
echo "$variable" >file
echo "$variable" | wc -l
(Real beginners also forget the quotes; usually use quotes unless you have a specific reason to omit them, at least until you understand quoting.)

Resources