shell scripting -findind the avg of 3 nos - linux

While finding the avg of 3 nos
I am writting the below statement
avg=$(( a + b + c))/3
echo "the avg is:"$avg
for a=7 b=7 c= 10
It is not printing the value of avg

revise to:
avg=$(((a + b + c) / 3))
echo "the avg is: " $avg
as a completed bash example:
#!/bin/bash
a=7
b=7
c=10
avg=$(((a + b + c) / 3))
echo "the avg is:" $avg

If you want the print at the same time as the set, you can use bc piped through to tee and /dev/tty and so:
avg=$(bc <<< "($a+$b+$c)/3" | tee /dev/tty)

Related

Plot graph using shell for total file count and date created

I want to create a histogram with total file count intervals of 50 on Y-axis and time created in weeks on X-axis (i.e if new files were created between week 1 and 2 and so on)
Something like
200, 150, 100 , 50 files created during a certain week
7, 14, 21, 28 days on Y-axis. Kind of lost on how to implement this. Any help is appreciated
Update: I am trying along these lines
find <dirname> -type f -ctime -1 -ctime -7 | wc -l
find <dirname> -type f -ctime +7 -ctime -14 | wc -l
Find the max number and use this as my X-axis upper limit. Then divide this number into equal intervals to plot my X-axis
This is a start using GNU awk for time functions (untested since you didn't provide concise, testable sample input that we could test against):
find "$1" -type f -printf '%T# %p\0' |
awk -v RS='\0' '
BEGIN {
nowSecs = systime()
}
{
fileName = gensub(/\S+\s+/,"",1)
fileModSecs = int($1)
fileAgeSecs = nowSecs - fileModSecs
fileAgeDays = int(fileAgeSecs / (24 * 60 * 60))
fileAgeWeeks = int(fileAgeDays / 7)
weekNr = fileAgeWeeks + 1
fileCnts[weekNr]++
numWeeks = (weekNr > numWeeks ? weekNr : numWeeks)
maxFileCnt = (fileCnts[weekNr] > maxFileCnt ? fileCnts[weekNr] : maxFileCnt)
print nowSecs, fileModSecs, fileAgeSecs, fileAgeDays, fileAgeWeeks, weekNr, fileName | "cat>&2"
}
END {
for (fileCnt=maxFileCnt; fileCnt>0; fileCnt--) {
for (weekNr=1; weekNr<=numWeeks; weekNr++) {
if (weekNr in fileCnts) {
char[weekNr] = "*"
}
printf "%s%s", char[weekNr], (weekNr<numWeeks ? OFS : ORS)
}
}
for (weekNr=1; weekNr<=numWeeks; weekNr++) {
printf "%s%s", weekNr, (weekNr<numWeeks ? OFS : ORS)
}
}
'
You need to figure out the details of the loops in the END section for printing the histogram but the above at least shows you how to get the count of files by week without calling find multiple times and hard-coding the number of days week by week.
Apologies being ksh instead of bash (bash level is near echo "Hello World") :)...
Would that do what you need ?
#!/bin/ksh
######################################
#
# statDirReport.sh
#
version="1.0"
# Andre Gelinas, 2018
#
######################################
#############
# Variables
#############
typeset -F2 SCALE
# Max value of X
X_SCALE=30
#############
# Main
#############
if [[ -n $1 ]]; then
DIRNAME=$1
else
print -n "Enter full path to stat : "; read DIRNAME
fi
if [[ ! -d $DIRNAME || ! -r $DIRNAME || ! -x $DIRNAME ]]; then
print "ERROR - Directory unusable - Exiting"
exit
fi
## Getting the data
CTIME1=1
CTIME2=0
for ((i=1;i<=4;i++)); do
CTIME2=$(($i*7))
FILE_COUNT[$i]=$(find $DIRNAME -type f -ctime +$CTIME1 -ctime -$CTIME2 | wc -l)
#To find late on the max amount
F_COUNT[${FILE_COUNT[$i]}]=${FILE_COUNT[$i]}
#
CTIME1=$CTIME2
done
#Doing some math
## Highest number of file
MAX_COUNT=${F_COUNT[-1]}
## Find the value of each tick
SCALE=$(($MAX_COUNT/$X_SCALE))
## Find the real length of the histogram for each week
## having the highest amount using full x scale (integer mathematics)
for ((i=1;i<=4;i++)); do
DATA_2_SCALE[$i]=$(((${FILE_COUNT[$i]}*$X_SCALE)/$MAX_COUNT))
done
# Getting the report
typeset -L2 Col1
typeset -L1 Col2
typeset -L$(($X_SCALE+5)) Col3
typeset -L5 Col4
Col1="Wk"
Col2=" "
Col3="Data"
Col4="Real"
clear
print "statDirReport v$version\tScale is #=$SCALE\n"
print "$Col1$Col2$Col3$Col4\n"
for ((i=1;i<=4;i++)); do
Col1=$i
Col2="|"
graph=""
Col4=${FILE_COUNT[$i]}
for ((j=1;j<=${DATA_2_SCALE[$i]};j++)); do
graph+="#"
done
Col3=$graph
print "$Col1$Col2$Col3$Col4"
done
Edit to modify to add dates as title for the histograms. Modify the last part, right after the "DATA_2_SCALE" loop, with :
#Setting the title of each histogram
## Finding how many sec since the beginning of time
TODAY_SEC=$(date +"%s")
## Finding real date for find range
SEC_PER_DAY=86400
lastDate=$(date -u -d #"$TODAY_SEC" +"%m/%d")
for ((i=1;i<=4;i++)); do
firstDate=$(date -u -d #"$(($TODAY_SEC-(7*$i*$SEC_PER_DAY)))" +"%m/%d")
WEEK[$i]=$firstDate" to "$lastDate" "
lastDate=$firstDate
done
# Getting the report
typeset -L15 Col1
typeset -L1 Col2
typeset -L$(($X_SCALE+5)) Col3
typeset -L5 Col4
Col1="Wk"
Col2=" "
Col3="Data"
Col4="Real"
clear
print "statDirReport v$version\tScale is #=$SCALE\n"
print "$Col1$Col2$Col3$Col4\n"
for ((i=1;i<=4;i++)); do
Col1=${WEEK[$i]}
Col2="|"
graph=""
Col4=${FILE_COUNT[$i]}
for ((j=1;j<=${DATA_2_SCALE[$i]};j++)); do
graph+="#"
done
Col3=$graph
print "$Col1$Col2$Col3$Col4"
done
Using feedgnuplot on a home directory:
dirname=~
e=0
for f in `seq 7 7 28` ; do
find "${dirname}" -type f -ctime +$e -ctime -$f | wc -l
e=$f
done 2> /dev/null |
feedgnuplot --terminal 'dumb 50,15' --with boxes --unset grid --exit
Output:
5500 +-+-----+-------+------+-------+-----+-+
5000 +-+ ********* + + + +-+
4500 +-+ * * ******** +-+
4000 +-+ * * * * +-+
3500 +-+ * * * * +-+
3000 +-+ * * * * +-+
2500 +-+ * ********* * +-+
2000 +-+ * * * * +-+
1500 +-+ * * * * +-+
1000 +-+ * + * + * + ********* +-+
500 +-+-********************************-+-+
0 1 2 3 4 5

Setting variable for use later in a case construct

I have this practice problem that is asking me to take my existing code and insert variables for letter grades into the existing 'IF' statements for use in a case construct that reads off a sentence based on the letter grade assigned. The problem I'm having is how to set the variable in the first place, ie. do I need to do a whole new IF/ECHO line for the variable? How do I even word it in the first place?
Would it be something like grade=A?
Here's what I have so far: (EDITED TO INCLUDE SUGGESTIONS)
#!/bin/bash
# Bash shell script to calculate student average
# Usage: ./grade1.sh
# Declare some integer type variables
declare -i test1
declare -i test2
declare -i test3
declare -i test4
declare -i lab
declare -i sum
echo
echo "=================="
echo "Grade Calculator "
echo "=================="
echo
read -p "Enter first name: " firstname
read -p "Enter last name: " lastname
echo
read -p "Enter test score 1: " test1
read -p "Enter test score 2: " test2
read -p "Enter test score 3: " test3
read -p "Enter test score 4: " test4
read -p "Enter lab score: " lab
sum=$test1+$test2+$test3+$test4+$lab
average=$((sum/5))
VAR1=A
VAR2=B
VAR3=C
VAR4=D
VAR5=F
if [ $average -ge 90 ]; then
echo "Course grade: $VAR1"
elif [ $average -ge 80 ]; then
echo "Course grade: $VAR2"
elif [ $average -ge 70 ]; then
echo "Course grade: $VAR3"
elif [ $average -ge 60 ]; then
echo "Course grade: $VAR4"
elif [ $average -le 60 ]; then
echo "Course grade: $VAR5"
fi
echo
echo "Grade results . . ."
echo "Student name: $firstname $lastname"
echo "Total points: $sum"
echo "Course average: $average"
echo
case $grade in
A) echo "An 'A' represents superior course work."
;;
B) echo "A 'B' represents above average course work."
;;
C) echo "A 'C' represents average course work."
;;
D) echo "A 'D' represents below average course work."
;;
F) echo "An 'F' represents failing course work."
;;
esac
The task, as you stated it, doesn't make very much sense .... it would make sense if we have the number of grades and the shresholds completely variable. But, well, it's a practice problem, so they might require you doing some nonsense, just for the exercise.
As it is an exercise, I'll give you some pointers, but don't write down the whole solution.
Assuming that you are really supposed to do what you are asking here (and did not misunderstood the task), you are supposed to replace the literal grades (A, B, C, D, F) by variables. Since you have 5 grades, you need either 5 variables or an array of 5 elements. You asked for variables, so for this exercise, this is the way to go.
Since you have 5 variables, you need to invent 5 different names, for example
this=A
is=B
a=C
silly=D
exercise=F # Note: grade E does not exist
You can write these definition somewhere before they are first used, and feel free to use variable names which suit you better. Now bash knows about these variables, you can use them, for instance:
elif [ $average -ge 60 ]; then
echo "Course grade: $silly"
Now to two things you didn't ask for, but might be interested to know:
First, the calculation of average in your code is incorrect. If you set all tests and the lab to 1, you will get an average of 5 (try it out).
Second, in your case statement, you are using a variable grade, which you don't set anywhere. For example, at the place that you found out that the grade is D (and you did find this out, because you do an echo of this fact), you should set the variable
grade=$silly
This is a circumstance where you can let the shell (bash specifically) help you with your problem by utilizing arrays to help you build your text output and let character classes help with matches in your case statement.
For example, since you know you are grading on the traditional A-F 90-50 breakpoints, you can create several arrays to allow you index all associated information, e.g.
ltrgrades=( A B C D F )
numgrades=( 90 80 70 60 50 )
prefixes=( An A A A An )
comments=( "superior"
"above average"
"average"
"below average"
"failing" )
A function can handle all your output needs per-student by passing the associated index as its first argument:
results() {
echo
echo "Grade results . . ."
echo "Student name : $firstname $lastname"
echo "Total points : $sum"
echo "Course average: $average"
echo
echo "Course grade : ${ltrgrades[$1]}"
echo
echo "${prefixes[$1]} '${ltrgrades[$1]}' represents ${comments[$1]} course work."
}
Utilizing a heredoc simplifies providing multi-line output:
## use a heredoc for multi-line text
cat << EOF
"=================="
"Grade Calculator "
"=================="
EOF
(note: you could utilize a heredoc within the results function)
Finally, you can use character classes as your case matches and '*' to mark the default case, e.g.
case "${average%.*}" in
1?? ) results 0;;
9[0-9] ) results 0;;
8[0-9] ) results 1;;
7[0-9] ) results 2;;
6[0-9] ) results 3;;
* ) results 4;;
esac
Putting it altogether, you could do:
#!/bin/bash
# Bash shell script to calculate student average
# Usage: ./grade1.sh
ltrgrades=( A B C D F )
numgrades=( 90 80 70 60 50 )
prefixes=( An A A A An )
comments=( "superior"
"above average"
"average"
"below average"
"failing" )
results() {
echo
echo "Grade results . . ."
echo "Student name : $firstname $lastname"
echo "Total points : $sum"
echo "Course average: $average"
echo
echo "Course grade : ${ltrgrades[$1]}"
echo
echo "${prefixes[$1]} '${ltrgrades[$1]}' represents ${comments[$1]} course work."
}
## use a heredoc for multi-line text
cat << EOF
"=================="
"Grade Calculator "
"=================="
EOF
read -p "Enter first name : " firstname
read -p "Enter last name : " lastname
echo
read -p "Enter test score 1: " test1
read -p "Enter test score 2: " test2
read -p "Enter test score 3: " test3
read -p "Enter test score 4: " test4
read -p "Enter lab score : " lab
sum=$((test1 + test2 + test3 + test4 + lab))
average=$(echo "scale=2; $sum / 5" | bc)
case "${average%.*}" in
1?? ) results 0;;
9[0-9] ) results 0;;
8[0-9] ) results 1;;
7[0-9] ) results 2;;
6[0-9] ) results 3;;
* ) results 4;;
esac
Example Use/Output
$ bash grades.sh
"=================="
"Grade Calculator "
"=================="
Enter first name : John
Enter last name : Doe
Enter test score 1: 85
Enter test score 2: 93
Enter test score 3: 94
Enter test score 4: 91
Enter lab score : 92
Grade results . . .
Student name : John Doe
Total points : 455
Course average: 91.00
Course grade : A
An 'A' represents superior course work.
$ bash grades.sh
"=================="
"Grade Calculator "
"=================="
Enter first name : Mary
Enter last name : Jane
Enter test score 1: 86
Enter test score 2: 93
Enter test score 3: 72
Enter test score 4: 71
Enter lab score : 77
Grade results . . .
Student name : Mary Jane
Total points : 399
Course average: 79.80
Course grade : C
A 'C' represents average course work.
$ bash grades.sh
"=================="
"Grade Calculator "
"=================="
Enter first name : Sally
Enter last name : Smith
Enter test score 1: 55
Enter test score 2: 61
Enter test score 3: 42
Enter test score 4: 58
Enter lab score : 59
Grade results . . .
Student name : Sally Smith
Total points : 275
Course average: 55.00
Course grade : F
An 'F' represents failing course work.
Since the grades are computed as a floating-point value using bc, you can, and probably should, handle rounding (e.g a 79.5 rounds to 80 while a 79.4 remains 79. You can handle that with another variable score with something similar to:
sum=$((test1 + test2 + test3 + test4 + lab))
average=$(echo "scale=2; $sum / 5" | bc)
fract=${average#*.}
score=${average%.*}
(( ${fract:0:1} >= '5')) && ((score++))
case $score in
1?? ) results 0;;
9[0-9] ) results 0;;
8[0-9] ) results 1;;
7[0-9] ) results 2;;
6[0-9] ) results 3;;
* ) results 4;;
esac
Now Mary Jane's grade with an average of 79.80 is rounded to 80 a B instead of a C. It's up to you to determine how rounding is handled, this is just one way to approach it. You could re-write results to show both the computed average and rounded score, e.g.
results() {
cat << EOF
Grade results . . .
Student name : $firstname $lastname
Total points : $sum
Course average: $average ($score)
Course grade : ${ltrgrades[$1]}
${prefixes[$1]} '${ltrgrades[$1]}' represents ${comments[$1]} course work.
EOF
}

Shell for loop, stopping at declaration

I'm trying to write a for loop that goes from 1 to 10, then calculates ( 1 through 10 mod 5) + 2. After that I want to display it like this (1 to 10 mod 5) + 2 = answer. However i'm getting an error at the beginning of the loop which is a syntax error.
for (( i = 0; i <= 10; i++)); do
calculate=(i % 5) + 2
echo ("("i "% 5) + 2" calculate)
done
Try these changes:
calculate=$(( i % 5 + 2 ))
# $(( ... )) is the shell's way to do arithmetic
echo "($i % 5) + 2 = " $calculate
# $x is a way to refer to the value of variable x
# (also inside a double-quoted string)
The for loop header is actually OK.

Shell scripting, finding average of loop results

I made a loop to find the results of calculating each number from (1 to 10 mod 5) + 2
for (( i = 0; i <= 10; i++))
do
calculate=$(( i % 5 + 2 ))
echo "($i % 5) + 2 = " $calculate
done
average=$(($calculate/10))
echo $average`
My problem is fixing my code so that I can take all the results of the loop and find the average of them
Its returning back 0 for the average
You have to keep a full total - ($calculate/10) is just the last iteration. Keep a running total initialized to zero before the loop total = 0 ... then add the calculated value to the total in each iteration of the loop total = $( $total + $calculate ) Then the average is total/10 (not calculate/10).
#!/bin/bash
total=0
for (( i = 0; i <= 10; i++))
do
calculate=$(( i % 5 + 2 ))
total=$(( $total + $calculate ))
echo "($i % 5) + 2 = " $calculate
done
#average=$(($calculate/10))
average=$(($total/10))
echo $average

Finding averages from reading a file using Bash Scripting

I am trying to write a bash script that reads a file 'names.txt' and will compute the average of peoples grades. For instance, names.txt looks something like this.
900706845 Harry Thompson 70 80 90
900897665 Roy Ludson 90 90 90
The script should read the line, print out the ID# of the person, the average of the three test scores and the corresponding letter grade. So the output needs to look like this
900706845 80 B
900897665 90 A
Heres what I have
#!/bin/bash
cat names.txt | while read x
do
$SUM=0; for i in 'names.txt'; do SUM=$(($SUM + $i));
done;
echo $SUM/3
done
I understand the echo will only print out the averages at this point, but I am trying to atleast get it to compute the averages before I attempt the other parts as well. Baby steps!
Like this maybe:
#!/bin/bash
while read a name1 name2 g1 g2 g3
do
avg=$(echo "($g1+$g2+$g3)/3" | bc)
echo $a $name1 $name2 $avg
done < names.txt
Output:
900706845 Harry Thompson 80
900897665 Roy Ludson 90
Customize gradeLetter for your own needs:
#!/bin/sh
awk '
function gradeLetter(g)
{
if (g >= 90) return "A";
if (g >= 80) return "B";
if (g >= 70) return "C";
if (g >= 60) return "D";
return "E"
}
{
avgGrade = ($(NF) + $(NF - 1) + $(NF - 2)) / 3;
print $1, avgGrade, gradeLetter(avgGrade)
}' names.txt
With a awk one-liner:
awk '{ AVG = int( ( $(NF-2) + $(NF-1) + $(NF) ) / 3 ) ; if ( AVG >= 90 ) { GRADE = "A" } else if ( AVG >= 80 ) { GRADE = "B" } else if ( AVG >= 70 ) { GRADE = "C" } else if ( AVG >= 60 ) { GRADE = "D" } else { GRADE = "F" } ; print $1, AVG, GRADE }' file
Let's look at the details:
awk '{
# Calculate average
AVG = int( ( $(NF-2) + $(NF-1) + $(NF) ) / 3 )
# Calculate grade
if ( AVG >= 90 ) { GRADE = "A" }
else if ( AVG >= 80 ) { GRADE = "B" }
else if ( AVG >= 70 ) { GRADE = "C" }
else if ( AVG >= 60 ) { GRADE = "D" }
else { GRADE = "F" }
print $1, AVG, GRADE
}' file
The ID#s and averages can be obtained as follows:
$ awk '{sum=0; for(i=3;i<=NF;i++) sum+=$i ; print $1, sum/3}' names.txt
900706845 80
900897665 90
Guessing at how to compute grades, one can do:
$ awk '{sum=0; for(i=3;i<=NF;i++) sum+=$i ; ave=sum/3; print $1, ave, substr("FFFFFDCBA", ave/10, 1) }' names.txt
900706845 80 B
900897665 90 A
The above solutions work for any number of tests but names are limited to 2 words. If there will always be three tests but names can be any number of words, then use:
$ awk '{ave=($(NF-2)+$(NF-1)+$NF)/3; print $1, ave, substr("FFFFFDCBA", ave/10, 1) }' names.txt
900706845 80 B
900897665 90 A

Resources