This is my first bash script and have ran into a little problem with setting the decimal precision. I have been tasked to create a bash script that calculates the area and circumference of a circle given the diameter of 20. This is what I currently have
#!/bin/bash
clear
diameter=$1 # storing first argument
radius=$(echo "scale=5;$diameter / 2" | bc) # setting radius
# echo "$radius"
# calculate area of a circle
area=$(echo "scale=5;3.14 * ($radius * $radius)" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "scale=5;2 * 3.14 * ($radius)" | bc -l) # C = 2(pi)(r)
echo "Circumference: $circum"
echo "Area: $area"
When I run the script it prints out
Circumference: 62.80000
Area: 314.00000
It should be printing out
Circumference: 62.83185
Area: 314.15926
I am not understand why it is not displaying the correct decimal values. I have given the scale=5 to display five decimal places which it is doing. I am confused why the zeros are showing up and not the true decimal values. Any help or suggestions would be greatly appreciated.
If pi were equal to 3.14, then your code is giving exact results.
To get the exact value of pi in bc code, use 4*a(1):
# calculate area of a circle
area=$(echo "scale=5;4*a(1) * ($radius * $radius)" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "scale=5;2 * 4*a(1) * ($radius)" | bc -l) # C = 2(pi)(r)
(This works because a(1) is the arctangent of 1 which is pi/4.)
With those changes, a sample run looks like:
$ bash circ.sh 20
Circumference: 62.83120
Area: 314.15600
The limited precision now is due to the choice of scale=5.
Still better precision
The code below keeps the best precision around until it is time to print and then prints in your desired precision:
#!/bin/bash
diameter=$1 # storing first argument
radius=$(echo "$diameter/2" | bc -l) # setting radius
# calculate area of a circle
area=$(echo "4*a(1)*$radius^2" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "2 * 4*a(1) * $radius" | bc -l) # C = 2(pi)(r)
printf "Circumference: %.5f\n" "$circum"
printf "Area: %.5f\n" "$area"
Example:
$ bash circ.sh 20
Circumference: 62.83185
Area: 314.15927
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I copied a shell script of SeisUnix(SU), a Linux based program for seismic processing.
#! /bin/sh
#File: Nmigcvp2.sh
# Create one panel for each migration velocity
# Each panel has the same "fldr" value
# The migration velocity is in key "offset"
# Total number of panels is in key "nvs"
# Set messages on
##set -x
#================================================
# USER AREA -- SUPPLY VALUES
#------------------------------------------------
# Seismic files
indata=Nstack4.su # SU format
outdata=Nmigcvp.su # migration Constant Velocity Panels
# Migration variables
cdpmin=900 # Start CDP value
cdpmax=1300 # End CDP value
dxcdp=16.667 # distance between adjacent CDP bins (m)
smig=1.0 # stretch factor (0.6 typical if vrms increasing)
# [the "W" factor] (Default=1.0)
vscale=1.0 # scale factor to apply to velocities (Default=1.0)
lstaper=20 # length of side tapers (traces) (Default=0)
lbtaper=100 # length of bottom taper (samples) (Default=0)
# Velocity panel variables
firstv=1400 # first velocity value
lastv=2000 # last velocity value
increment=200 # velocity increment
numVtest=100 # use to limit number of velocity panels
# otherwise, use very large value (100)
#================================================
# Compute number of velocity panels
numV=`echo "( ( $lastv - $firstv ) / $increment ) + 1" |bc -l
if [ $numVtest -lt $numV ] ; then
numV=$numVtest
fi
#------------------------------------------------
# FILE DESCRIPTIONS
# tmp1 = binary temp file of input data
#------------------------------------------------
cp $indata tmp1
migV=$firstv
echo " "
#------------------------------------------------
# Loop through Migration Constant Velocity Panels
# Each panel has the same "fldr" value
# Panel migration velocity is in key "offset"
# Total number of panels (numV) is in key "nvs"
#------------------------------------------------
i=1
while [ $i -le $numV ]
do
echo " iteration number = $i Velocity = $migV"
suwind < tmp1 key=cdp min=$cdpmin max=$cdpmax |
sushw key=fldr a=$i |
sushw key=offset a=$migV |
sushw key=nvs a=$numV |
sustolt cdpmin=$cdpmin cdpmax=$cdpmax dxcdp=$dxcdp \
tmig=0 vmig=$migV smig=$smig vscale=$vscale \
lstaper=$lstaper lbtaper=$lbtaper \
>> $outdata
i=`expr $i + 1`
migV=`expr $migV + $increment`
done
#------------------------------------------------
# Remove files and exit
#------------------------------------------------
echo " "
echo " Output file = $outdata"
echo " "
rm -f tmp*
exit
When I run the script I get a syntax error.
./Nmigcvp2.sh: 33: Syntax error: end of file unexpected (expecting "done")
numV=`echo "( ( $lastv - $firstv ) / $increment ) + 1" |bc -l`
You're missing a ` backtick char at end of line here. If you use a colored syntax editor it is easier to find this kind of syntax error.
In VIM for example use set syntax & colorscheme elflord.
You can also numerate lines set nu.
I am trying to plot 4d plot using splot (x y z value). I would like to have 4th column shown as heat color. Up to this point, I am fine. What I can't figure out, after searching answers online, is to have the color of the dots being transparent but with different transparency based on their value.
For example, let's say I have the following data set:
0 0 0 0.1
0 0 1 0.2
0 1 0 0.2
0 1 1 2
1 0 0 1
1 0 1 3
1 1 0 0.5
1 1 1 4
Now, I want to make the colorbar (for the 4th column) to be as such: the closer the 4th column value is to 1, the more transparent the dot/point in the figure it will be. All the places I've look can only give me uniform transparency for the entire colorbar.
I wonder if anyone has dealt with this before, or has an idea how to do this.
Thank you very much!
I did not fully understand how you want the transparency to depend on the value, so I will give a general answer, where you can substitute the transparency function for your own.
While you can specify transparency for line colors, this appears to not be possible when using palette, which would be the most straightforward way to achieve what you want. The furthest you can go with only using gnuplot is to make the colors appear transparent, as in the following script, where in.data is a file with your sample data.
#!/usr/bin/env gnuplot
set term pngcairo
in_data = "in.data"
set out "out.png"
# function to combine color and alpha channels with white background
# 0: no transparency, 1: fully transparent
make_transparent(x1, t) = (1-t)*x1 + t
# a function to decide transparency
# the input and output must be in range of [0,1]
#get_transparency(x1) = 0 # no transparency
#get_transparency(x1) = 1 # fully transparent
get_transparency(x1) = 1 - x1 # smaller values are more transparent
# convenience function to truncate values
minval(x1, x2) = x1<x2?x1:x2
maxval(x1, x2) = x1>x2?x1:x2
truncval(x1, xmin, xmax) = maxval(minval(x1, xmax), xmin)
trunc(x1) = truncval(x1, 0, 1)
# the default palette consists of rgbfunctions 7,5,15
# we redefine their transparency enabled versions here
# the input and output must be in range of [0,1]
# see other formulae with "show palette rgbformulae" command in gnuplot
f7(x1) = make_transparent(sqrt(x1) , get_transparency(x1))
f5(x1) = make_transparent(x1**3 , get_transparency(x1))
f15(x1) = make_transparent(trunc(sin(2*pi*x1)), get_transparency(x1))
set palette model RGB functions f7(gray),f5(gray),f15(gray)
splot in_data palette
This script assumes that the background is white, but can be adapted to any other solid background. It falls apart once the points start overlapping however.
To get real transparency, you would need to plot each data point as a separate line and give it a distinct line color. This could be achieved by pre-processing the data, as in the following bash script.
#!/usr/bin/env bash
set -eu
in_data="in.data"
out_png="out.png"
pi=3.141592653589793
# function to convert data value into rgba value
function value2rgba()
{
# arguments to function
local val="${1}"
local min="${2}"
local max="${3}"
# normalized value
local nval=$( bc -l <<< "(${val}-${min})/(${max}-${min})" )
#### alpha channel value ####
local alpha="$( bc -l <<< "255 * (1-${nval})" )"
# round to decimal
alpha=$( printf "%0.f" "${alpha}" )
#### red channel value ####
# rgbformulae 7 in gnuplot
local red="$( bc -l <<< "255 * sqrt(${nval})" )"
# round to decimal
red=$( printf "%0.f" "${red}" )
#### green channel value ####
# rgbformulae 5 in gnuplot
local red="$( bc -l <<< "255 * sqrt(${nval})" )"
local green="$( bc -l <<< "255 * ${nval}^3" )"
# round to decimal
green=$( printf "%0.f" "${green}" )
#### blue channel value ####
# rgbformulae 15 in gnuplot
local blue="$( bc -l <<< "255 * s(2*${pi}*${nval})" )"
# round to decimal
blue=$( printf "%0.f" "${blue}" )
# make sure blue is positive
if (( blue < 0 ))
then
blue=0
fi
### whole rgba value
local rgba="#"
rgba+="$( printf "%02x" "${alpha}" )"
rgba+="$( printf "%02x" "${red}" )"
rgba+="$( printf "%02x" "${green}" )"
rgba+="$( printf "%02x" "${blue}" )"
echo "${rgba}"
}
# data without blank lines
data="$( sed -E "/^[[:space:]]*$/d" "${in_data}" )"
# number of lines
nline=$( wc -l <<< "${data}" )
# get the minimum and maximum value of the 4-th column
min_max=( $( awk '{ print $4 }' <<< "${data}" | sort -g | sed -n '1p;$p' ) )
# array of colors for each point
colors=()
while read -r line
do
colors+=( $( value2rgba "${line}" "${min_max[#]}" ) )
done < <( awk '{ print $4 }' <<< "${data}" )
# gather coordinates into one row
coords=( $( awk '{ print $1,$2,$3 }' <<< "${data}" ) )
gnuplot << EOF
set term pngcairo
set out "${out_png}"
\$DATA << EOD
${coords[#]}
EOD
nline=${nline}
colors="${colors[#]}"
unset key
splot for [i=0:nline-1] \$DATA \
u (column(3*i+1)):(column(3*i+2)):(column(3*i+3)) \
pt 1 lc rgb word(colors, i+1)
EOF
These scripts were tested with gnuplot 5.
How do I truncate a floating point number using bc
e.g if I do
echo '4.2-1.3' | bc
which outputs 2.9 how I get it to truncate/use floor to get 2
Use / operator.
echo '(4.2-1.3) / 1' | bc
Dividing by 1 works ok if scale is 0 (eg, if you start bc with bc and don't change scale) but fails if scale is positive (eg, if you start bc with bc -l or increase scale). (See transcript below.) For a general solution, use a trunc function like the following:
define trunc(x) { auto s; s=scale; scale=0; x=x/1; scale=s; return x }
Transcript that illustrates how divide by 1 by itself fails in the bc -l case, but how trunc function works ok at truncating toward zero:
> bc -l
bc 1.06.95
[etc...]
for (x=-4; x<4; x+=l(2)) { print x,"\t",x/1,"\n"}
-4 -4.00000000000000000000
-3.30685281944005469059 -3.30685281944005469059
-2.61370563888010938118 -2.61370563888010938118
-1.92055845832016407177 -1.92055845832016407177
-1.22741127776021876236 -1.22741127776021876236
-.53426409720027345295 -.53426409720027345295
.15888308335967185646 .15888308335967185646
.85203026391961716587 .85203026391961716587
1.54517744447956247528 1.54517744447956247528
2.23832462503950778469 2.23832462503950778469
2.93147180559945309410 2.93147180559945309410
3.62461898615939840351 3.62461898615939840351
define trunc(x) { auto s; s=scale; scale=0; x=x/1; scale=s; return x }
for (x=-4; x<4; x+=l(2)) { print x,"\t",trunc(x),"\n"}
-4 -4
-3.30685281944005469059 -3
-2.61370563888010938118 -2
-1.92055845832016407177 -1
-1.22741127776021876236 -1
-.53426409720027345295 0
.15888308335967185646 0
.85203026391961716587 0
1.54517744447956247528 1
2.23832462503950778469 2
2.93147180559945309410 2
3.62461898615939840351 3
Try the following solution. It will truncate anything after the decimal point without a problem:
echo 'x = 4.2 - 1.3; scale = 0; x / 1' | bc -l
echo 'x = l(101) / l(10); scale = 0; x / 1' | bc -l
You can make the code a tad shorter by performing calculations directly on the numbers:
echo 'scale = 0; (4.2 - 1.3) / 1' | bc -l
echo 'scale = 0; (l(101) / l(10)) / 1' | bc -l
In general, you can use this function to get only the integer part of a number:
define int(x) {
auto s;
s = scale;
scale = 0;
x /= 1; /* This will have the effect of truncating x to its integer value */
scale = s;
return (x);
}
Save that code into a file (let's call it int.bc) and run the following command:
echo 'int(4.2 - 1.3);' | bc -l int.bc
The variable governing the amount of decimals on division is scale.
So, if scale is 0 (the default), dividing by 1 would truncate to 0 decimals:
$ echo '(4.2-1.3) / 1 ' | bc
2
In other operations, the number of decimals is calculated from the scale (number of decimals) of each operand. In add, subtract and multiplication, for example, the resulting scale is the biggest of both:
$ echo ' 4.2 - 1.33333333 ' | bc
2.86666667
$ echo ' 4.2 - 1.333333333333333333 ' | bc
2.866666666666666667
$ echo ' 4.2000 * 1.33 ' | bc
5.5860
Instead, in division, the number of decimals is strictly equal to th evalue of the variable scale:
$ echo 'scale=0;4/3;scale=3;4/3;scale=10;4/3' | bc
1
1.333
1.3333333333
As the value of scale has to be restored, it is better to define a function (GNU syntax):
$ echo ' define int(x){ os=scale;scale=0;x=x/1;scale=os;return(x) }
int( 4.2-1.3 )' | bc
2
Or in older POSIX language:
$ echo ' define i(x){
o=scale;scale=0;x=x/1;scale=o;return(x)
}
i( 4.2-1.3 )' | bc
2
You say:
truncate/use floor
And those are not the same thing in all cases. The other answers so far only show you how to truncate (i.e. "truncate towards zero" i.e. "discard the part after the decimal").
For negative numbers, the behavior is different.
To wit:
truncate(-2.5) = -2
floor(-2.5) = -3
So, here is a floor function for bc:
# Note: trunc(x) is defined as noted elsewhere in the other answers
define floor(x) {
auto t
t=trunc(x)
if (t>x) {
return t-1
} else {
return t
}
}
Aside:
You can put this, and other helper functions, in a file. For instance, I have this alias in my shell:
alias bc='bc -l ~/.bcinit'
And so whenever I run bc, I get all my utility functions from ~/.bcinit available by default.
Also, there is a good list of bc functions here: http://phodd.net/gnu-bc/code/funcs.bc
You may do something like this:
$ printf "%.2f\n" $(echo "(4530 / 4116 - 1) * 100" | bc -l)
10.06
Here I am trying to find the % change. Not purely bc though.
I want to generate a series of files in which the file name of each file shall be increased by 1 (File1.txt, File2.txt, File3.txt, ... FileN.txt) where N = 250
Each file has 2 lines.
AAAXXX (where XXX = 001 to 250 - automatic increased for each file)
BBBYYY (where YYY = 3 digit random number )
Example:
File1.txt:
AAA001
BBB175
File5.txt:
AAA005
BBB067
File102.txt:
AAA102
BBB765
I'm a newbie using Ubuntu Linux 12.04 - but I'm hoping someone can assist.
You can do it as follows:
#!/bin/bash
for i in {1..250}
do
printf "AAA%03d\nBBB%03d" ${i} $(($RANDOM % 1000)) > File${i}.txt
done
Explanation:
for i in {1..250} - bash way of specifying iteration from 1 to 250, increment size of 1.
printf - shell printf command - used to print formatted string
AAA - string literal (means "exactly as written")
%03d - formatted string, this prints a decimal number padded with 3 zero's in front.
\n - newline
BBB - another string literal
%03d - same as before
${i} - this is the value used in the first formatted string (%03d)
$(($RANDOM % 1000)) - $RANDOM is a system variable that provides a random number for you each time you access it. The % 1000 to take the modulo so you get a range betwee 0-999. This is used in the 2nd formatted string (%03d)
> File${i}.txt: output redirection; creates and saves to a file (overwrites if file already exists.
Here's a quick one-liner that might start you off:
for i in {1..250}; do printf "AAA%03d\nBBB%03d" $i $(($RANDOM % 1000)) > "File${i}.txt"; done
Using bash:
for i in {1..250}; do printf "AAA%03d\nBBB%03d\n" "$i" "$((RANDOM%1000))" > "File$i.txt"; done
You can write a bash script for this
#!/bin/bash
for (( i=1; i<=250; i++ ))
do
NUMBER=$[ ( $RANDOM % 999 ) + 100 ]
echo "AAA$i BBB$NUMBER" > File$i.txt
done
Under windows, when I need to perform a basic calculations, I use a built-in calculator. Now I would like to find out what is the common way if you only have a shell.
Thanks
From this web page (for csh and derivatives, since you asked):
% # x = (354 - 128 + 52 * 5 / 3)
% echo Result is $x
Result is 174
and
% set y = (354 - 128 + 52 / 3)
% echo Result is $y
Result is 354 - 128 + 52 / 3
notice the different results.
Personally, I stick to /bin/sh and call awk or something (for maximal portability), or others have exhibited the bash approach.
You can use dc. Or bc.
There are many good solutions given here, but the 'classic' way to do arithmetic in the shell is with expr:
$ expr 1 + 1
2
expr has a sensible return value, so that it succeeds when the expression evaluates to a non-zero value allowing code (in a Bourne shell) like:
$ op="1 + 1"
$ if expr $op > /dev/null; then echo "$op is not zero"; fi
1 + 1 is not zero
or (if using a shell that supports arrays):
$ op=(8 \* 3)
$ if expr "${op[#]}" > /dev/null; then echo "${op[#]} is not zero"; fi
8 * 3 is not zero
Note that the if syntax in Bourne shells is completely different than in the csh family, so this is slightly less useful and you need to check against the value of #?.
Bash supports basic (integer only) arithmetic inside $(( )):
$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0
$ myvar=$(( $myvar + 1 ))
$ echo $myvar
57
(example copied straight from the IBM link)
More in-depth discussion of bash arithmetic
And you can always use the python interpreter, it's normally included in linux distros.
http://docs.python.org/tutorial/introduction.html#using-python-as-a-calculator
$ python
Python 2.6.2 (r262:71605, Apr 14 2009, 22:40:02) [MSC v.1500 32 bit (Intel)]
Type "help", "copyright", "credits" or "license" for more information.
>>> 2+2
4
>>> # This is a comment
... 2+2
4
>>> 2+2 # and a comment on the same line as code
4
>>> (50-5*6)/4
5
>>> # Integer division returns the floor:
... 7/3
2
>>> 7/-3
-3
>>> # use float to get floating point results.
>>> 7/3.0
2.3333333333333335
The equal sign ('=') is used to assign a value to a variable. Afterwards, no result is displayed before the next interactive prompt:
>>> width = 20
>>> height = 5*9
>>> width * height
900
And of course there's the math module which should solve most of your calculator needs.
>>> import math
>>> math.pi
3.1415926535897931
>>> math.e
2.7182818284590451
>>> math.cos() # cosine
>>> math.sqrt()
>>> math.log()
>>> math.log10()
You can also use Perl easily where bc or expr are not powerful enough:
$ perl5.8 -e '$a=1+2; print "$a\n"'
3
Alternative option is to use the built in BC command