Bash Script | Subtracting percentage from free disk space - bash

Trying to subtract a percentage from a variable, preferable with floating point (11.3%) else 12%
freespace=100
subtract=$(($freespace-$freespace*.113))
Update 1:
Here is an explanation of what I am trying to accomplish:
I am reducing my actual free space by 11.3%. if the file that I am copying is bigger than the subtracted free space then the script will prompt the user to create more space. bc command returns a floating-point. For this to work the output of bc must be without decimal points. df -m command outputs in megabytes and without decimal points. I don't you can compare 18000 MB with 120000.00 MB (bc output) in bash.
source="/some/dir"
destination="/some/dir
freespace="$(df -m "${destination}" | tail -1 | awk '{print $4}')"
reduced_percentage=".113" # minus 11.3 percent of actual freespace
reduced_freespace=$(bc <<< "$freespace * (1-$reduced_percentage)")
source_size="$(du -sm "${files}" | tail -1 | awk '{print $1}')"
if [[ "${reduced_freespace}" -lt "${source_size}" ]] ; then
read -r -n 1 -p "you do not have enough free space, create free space to continue..."
elif [[ "${reduced_freespace}" -gt "${source_size}" ]] ; then
some commands
fi
done

you'll want to learn the bc command.
Try this, and then read more here.
freespace=100
pct=.113
subtract=$(bc <<< "$freespace * (1-$pct)")

You can use awk for float calculation
$awk -v freespace=100 'BEGIN {print freespace-(freespace) * 0.113}'
88.7
$

Related

Converting string to floating point number without bc in bash shell script

I'm getting load average in a bash shell script like so
load=`echo $(cat /proc/loadavg | awk '{print $1}')`
I know piping to bc
load=`echo $(cat /proc/loadavg | awk '{print $1}') \> 3 | bc -l`
is used in almost all examples of how to cast $load as an int but this box does not have bc installed and I am not allowed to add it.
I tried
int=`perl -E "say $load - 0"`
I tried
int=${load%.*}
I tried
int=`printf -v int %.0f "$load"`
What I want to be able to do is
if [ "$int" -gt 3.5 ]; then
How do I get that to evaluate as intended?
You can use awk to produce a success/failure depending on the condition:
# exit 0 (success) when load average greater than 3.5, so take the branch
if awk '{ exit !($1 > 3.5) }' /proc/loadavg; then
# load average was greater than 3.5
fi
Unfortunately, since "success" is 0 in the shell, you have to invert the logic of the condition to make awk exit with the required status. Obviously, you can do this in a number of ways, such as changing > to <=.
You don't need any external tools (like awk) to read this stuff. Load average from /proc/loadavg is always formatted with two decimal places, so you can do this:
read load _ < /proc/loadavg
if [ ${load/./} -gt 350 ]; then
# do something
fi

How to calculate extra large numbers with bc in bash

I have a file with 1800 lines that look like this
600.76
600.66
700.44
566.66
Ect..
I made a bash script to calculate the mean.
Now I first made a variable to count the total column lines like:
Lines="$(awk 'END{print NR}' file.txt)"
Then another variable for the sum of that column like this:
Sum="$(awk '{s+1=$1}END {print s}' file.txt)"
Lastly I'm finding the mean like this:
Echo "scale=2 ; $Sum / $Lines" | bc
With debugging enabled It returns:
+echo 'scale=2 ; 1.72161e+06 / 1800'
(Standard_1): syntax error
I realize now bc doesn't do scientific notation but how do I get around this.
I'm OK with short handing the decimal by restricting it to 2 or 3 places.
Unnecessary to use awk. Simple oneliner can do the job.
echo "scale=2; ("$(paste -sd+ file.txt)")"/$(wc -l <file.txt)|bc
Use bc -l for both summation and final division:
sum=0
count=0
while read number; do
number=$(printf "%f\n" $number) # get rid of scientific notation
sum=$(echo "$sum" '+' "$number" | bc -l)
count=$((count + 1))
done < input
avg=$(echo $sum / $count | bc -l)
echo $avg

How to display number to two decimal places, even zero .00 using BC or DC [duplicate]

Greetings!
I uses bс to make some calculations in my script. For example:
bc
scale=6
1/2
.500000
For further usage in my script I need "0.500000" insted of ".500000".
Could you help me please to configure bc output number format for my case?
In one line:
printf "%0.6f\n" $(bc -q <<< scale=6\;1/2)
Just do all your calculations and output in awk:
float_scale=6
result=$(awk -v scale=$floatscale 'BEGIN { printf "%.*f\n", scale, 1/2 }')
As an alternative, if you'd prefer to use bc and not use AWK alone or with 'bc', Bash's printf supports floating point numbers even though the rest of Bash doesn't.
result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)
result=$(printf '%*.*f' 0 "$float_scale" "$result")
The second line above could instead be:
printf -v $result '%*.*f' 0 "$float_scale" "$result"
Which works kind of like sprintf would and doesn't create a subshell.
Quick and dirty, since scale only applies to the decimal digits and bc does not seem to have a sprintf-like function:
$ bc
scale = 6
result = 1 / 2
if (0 <= result && result < 1) {
print "0"
}
print result;
echo "scale=3;12/7" | bc -q | sed 's/^\\./0./;s/0*$//;s/\\.$//'
I believe here is modified version of the function:
float_scale=6
function float_eval()
{
local stat=0
local result=0.0
if [[ $# -gt 0 ]]; then
result=$(echo "scale=$float_scale; $*" | bc -q | awk '{printf "%f\n", $0}' 2>/dev/null)
stat=$?
if [[ $stat -eq 0 && -z "$result" ]]; then stat=1; fi
fi
echo $result
return $stat
}
Can you put the bc usage into a little better context? What are you using the results of bc for?
Given the following in a file called some_math.bc
scale=6
output=1/2
print output
on the command line I can do the following to add a zero:
$ bc -q some_math.bc | awk '{printf "%08f\n", $0}'
0.500000
If I only needed the output string to have a zero for formatting purposes, I'd use awk.

Get 20% of lines in File randomly

This is my code:
nb_lignes=`wc -l $1 | cut -d " " -f1`
for i in $(seq $nb_lignes)
do
m=`head $1 -n $i | tail -1`
//command
done
Please how can i change it to get Get 20% of lines in File randomly to apply "command" on each line ?
20% or 40% or 60 % (it's a parameter)
Thank you.
This will randomly get 20% of the lines in the file:
awk -v p=20 'BEGIN {srand()} rand() <= p/100' filename
So something like this for the whole solution (assuming bash):
#!/bin/bash
filename="$1"
pct="${2:-20}" # specify percentage
while read line; do
: # some command with "$line"
done < <(awk -v p="$pct" 'BEGIN {srand()} rand() <= p/100' "$filename")
If you're using a shell without command substitution (the <(...) bit), you can do this - but the body of the loop won't be able to have any side effects in the outer script (e.g. any variables it sets won't be set anymore once the loop completes):
#!/bin/sh
filename="$1"
pct="${2:-20}" # specify percentage
awk -v p="$pct" 'BEGIN {srand()} rand() <= p/100' "$filename" |
while read line; do
: # some command with "$line"
done
Try this:
file=$1
nb_lignes=$(wc -l $file | cut -d " " -f1)
num_lines_to_get=$((20*${nb_lignes}/100))
for (( i=0; i < $num_lines_to_get; i++))
do
line=$(head -$((${RANDOM} % $nb_lignes)) $file | tail -1)
echo "$line"
done
Note that ${RANDOM} only generates numbers less than 32768 so this approach won't work for large files.
If you have shuf installed, you can use the following to get a random line instead of using $RANDOM.
line=$(shuf -n 1 $file)
you can do it with awk.see below:
awk -v b=20 '{a[NR]=$0}END{val=((b/100)*NR)+1;for(i=1;i<val;i++)print a[i]}' all.log
the above command prints 20% of all the lines starting from begining of the file.
you just have to change the value of b on command line to get the required % of lines.
tested below:
> cat temp
1
2
3
4
5
6
7
8
9
10
> awk -v b=10 '{a[NR]=$0}END{val=((b/100)*NR)+1;for(i=1;i<val;i++)print a[i]}' temp
1
> awk -v b=20 '{a[NR]=$0}END{val=((b/100)*NR)+1;for(i=1;i<val;i++)print a[i]}' temp
1
2
>
shuf will produce the file in a randomized order; if you know how many lines you want, you can give that to the -n parameter. No need to get them one at a time. So:
shuf -n $(( $(wc -l < $FILE) * $PCT / 100 )) "$file" |
while read line; do
# do something with $line
done
shuf comes standard with GNU/Linux distros afaik.

Why is my code not working as I want it to?

I have this code:
total=0;
ps -u $(whoami) --no-headers | awk {'print $1'} | while read line;
do vrednost=$(pmap $line | tail -n1 | column -t | cut -d" " -f3 | tr "K" " ");
total=$(( vrednost + total ))
echo $total
done
echo total: $total
As you can see, my code sums usage of all my processes. When I echo my total every time in while, it is working ok, but at the end... When i want total to be a value (echo total: $total) it is still zero. but before (in while) has right value.
BASH FAQ #24: "I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?"
#!/bin/bash
while read ...
do
...
done < <(ps ...)
Okay, pick and choose. You can either do it in BASH or AWK, but don't do both. You've seen a BASH example, here's an AWK example:
ps -e -o user -o vsz | awk -v USER="$(whoami)" '
BEGIN {TOTAL = 0}
END {print "Total is " TOTAL}
{
if ($1 == USER) {
TOTAL += $2
}
}
'
Awk is like a programming language that assumes a loop (like perl -n) and processes each line in the file. Each field (normally separated by whitespace) is given a $ variable. The first is $1, the second is $2, etc.
The -v option allows me to define an awk variable (in this case USER) before I run awk.
The BEGIN line is what I want to do before I run my awk script. In this case, initialize TOTAL to zero. (NOTE: This really isn't necessary since undefined variables automatically are given a value of zero). The END line is what I want to do afterwards. In this case, print out my total.
So, if the first field ($1) is equal to my user, I'll add the second field (the vsize) to my total.
All Awk programs are surrounded by {...} and they usually have single quotes around them to prevent shell interpolation of $1, etc.
Try this
#!/bin/bash
total=0;
for line in `ps -u $(whoami) --no-headers | awk {'print $1'}`;
do
vrednost=$(pmap $line | tail -n1 | column -t | cut -d" " -f3 | tr "K" " ");
total=$(( $vrednost + $total ))
echo $total
done
echo "total: $total"
Ignacio's answer is fine, but process substitution is not portable. And there is a simpler solution. You need to echo the total in the same subshell in which it is calculated:
... | { while read line; do ...; done; echo total: $total; }
Let's cut down on the number of extra processes you need :)
declare -i total=0
for size in $( ps -u $(whoami) --no-header -o vsz ); do
total+=$size
done
echo $total
First, use various options for ps to generate the desired list of process sizes, in kilobytes. Iterate over that list using a bash for-loop, keeping a running total in a parameter declared with the 'integer' attribute for easy arithmetic. The desired sum is now in total, ready for whatever use you need. The sum includes the memory used by the ps process itself.
Using while (Dennis' suggestion) and avoiding process substitution (William's suggestion):
ps -u $(whoami) --no-header -o vsz | {
while read -r var; do
((total+=$var))
done
echo $total
}
(For real one-liner, here's a dc command that I borrowed from https://stackoverflow.com/a/453290/1126841:
ps -u $(whoami) --no-header -o vsz | dc -f - -e '[+z1<r]srz1<rp'
This sum includes the memory used by the ps and dc commands themselves.)

Resources