mirror of
https://github.com/caperren/school_archives.git
synced 2025-11-09 13:41:13 +00:00
235 lines
6.6 KiB
Bash
235 lines
6.6 KiB
Bash
#!/bin/bash
|
|
########## Programmer Info ############
|
|
# Name: Corwin Perren
|
|
# OSU ID: 931759527
|
|
# Assignment: Assignment 1 - stats
|
|
# Filename: stats
|
|
|
|
########## Global Variables ############
|
|
# Used for transposing the axis on a two dimensional array
|
|
awk_transpose='{
|
|
for ( i=1; i <= NF; i++ )
|
|
row[i] = row[i]((row[i])?" ":"")$i
|
|
}
|
|
|
|
END{
|
|
for ( x = 1; x <= length(row) ; x++ )
|
|
print row[x]
|
|
}'
|
|
|
|
# Used to properly round the floating point values
|
|
awk_proper_rounding='
|
|
{printf("%d\n",$1 + 0.5)}
|
|
'
|
|
|
|
# Stores the PID used to name and delete temp files
|
|
master_pid=$!
|
|
|
|
########## Functions ###########
|
|
# Error function for when user input is wrong
|
|
show_usage_error ()
|
|
{
|
|
echo "Usage: stats {-rows|-cols} [file]" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Error function for when a user feeds in an empty file
|
|
show_empty_file_error ()
|
|
{
|
|
echo "stats: Input empty. Please provide valid input." >&2
|
|
exit 1
|
|
}
|
|
|
|
# Error function for when the file doesn't exist or can't be accessed
|
|
show_invalid_file_error ()
|
|
{
|
|
echo "stats: Cannot read file. Please verify file exists or check permissions." >&2
|
|
exit 1
|
|
}
|
|
|
|
# Function to delete temporary files
|
|
remove_temp_if_exist ()
|
|
{
|
|
rm -f ${master_pid}"_"transposed
|
|
rm -f ${master_pid}"_"temp
|
|
}
|
|
|
|
# Function to handle file cleanup and returning an error when an interrupt happens
|
|
handle_unexpected_termination_error ()
|
|
{
|
|
remove_temp_if_exist
|
|
echo "CTRL+C received. Exiting." >&2
|
|
exit 1
|
|
}
|
|
|
|
#####################################################
|
|
#####################################################
|
|
########## "stats" script "main"-ish code ###########
|
|
|
|
# Handle unexpected termination
|
|
trap handle_unexpected_termination_error INT HUP TERM
|
|
|
|
# Determine if we're getting data from stdin or a file, error if neither
|
|
if [ $# -eq 0 ] || [ $# -gt 2 ]; then
|
|
show_usage_error
|
|
elif [ $# -eq 1 ]; then
|
|
is_stdin=1
|
|
elif [ $# -eq 2 ]; then
|
|
is_stdin=0
|
|
fi
|
|
|
|
# Determine if we're doing statistics based on columns or rows, error if neither
|
|
if [[ $1 == -r* ]]; then
|
|
is_rows=1
|
|
elif [[ $1 == -c* ]]; then
|
|
is_rows=0
|
|
else
|
|
show_usage_error
|
|
fi
|
|
|
|
# If the input is a file, make sure we can open it and that it exists
|
|
if [ ${is_stdin} -eq 0 ]; then
|
|
if [ ! -e $2 ] || [ ! -r $2 ] || [ ! -f $2 ]; then
|
|
show_invalid_file_error
|
|
fi
|
|
fi
|
|
|
|
# If stats should be on columns, transpose the table so the columns are the new rows and rows are the new columns
|
|
# This will make it so the same stats math can be used to generate the correct data
|
|
# If the data is coming from stdin and needs to be columns, it makes a temp file, stores the data in it, and then
|
|
# transposes it just as if it were a file being fed in as an argument
|
|
# If the flags here are anything but a standard in piping with rows, it opens the file with a file descriptor for access
|
|
# This also handles showing an error if the input from stdin with -cols is empty
|
|
if [ ${is_rows} -eq 0 ] && [ ${is_stdin} -eq 0 ]; then
|
|
cat $2 | awk "${awk_transpose}" > ${master_pid}"_"transposed
|
|
exec 3<> ${master_pid}"_"transposed
|
|
elif [ ${is_rows} -eq 0 ] && [ ${is_stdin} -eq 1 ]; then
|
|
line_count=0
|
|
while read current_line
|
|
do
|
|
echo -e ${current_line} >> ${master_pid}"_"temp
|
|
((line_count = line_count + 1))
|
|
done
|
|
|
|
if [ ${line_count} -eq 0 ]; then
|
|
show_empty_file_error
|
|
fi
|
|
|
|
cat ${master_pid}"_"temp | awk "${awk_transpose}" > ${master_pid}"_"transposed
|
|
exec 3<> ${master_pid}"_"transposed
|
|
is_stdin=0
|
|
|
|
elif [ ${is_rows} -eq 1 ] && [ ${is_stdin} -eq 0 ]; then
|
|
exec 3<> $2
|
|
fi
|
|
|
|
# Now we perform the stats math operations on the data
|
|
line_count=0
|
|
declare -a averages
|
|
declare -a medians
|
|
|
|
while [ 1 ];
|
|
do
|
|
# We read in the current line
|
|
if [ ${is_stdin} -eq 1 ]; then
|
|
read current_line
|
|
else
|
|
read -u 3 current_line
|
|
fi
|
|
|
|
# Here we get the result code from read, which tells us if there's data left
|
|
read_result=$?
|
|
|
|
# If there was no data, and we haven't looped yet, the file is empty and we error
|
|
# Otherwise, it means we've reached the end of the file and it's time to leave the loop
|
|
if [ ${read_result} -eq 1 ] && [ ${line_count} -eq 0 ]; then
|
|
show_empty_file_error
|
|
elif [ ${read_result} -eq 1 ]; then
|
|
break
|
|
fi
|
|
|
|
# Initialize variables for doing the calculations
|
|
sum=0
|
|
count=0
|
|
avg=0
|
|
newline="\n"
|
|
numbers_string=""
|
|
|
|
# This part does the summing and adds the numbers to a new string so it can be parsed by sort
|
|
for word in ${current_line}
|
|
do
|
|
numbers_string=${numbers_string}${newline}${word}
|
|
((sum = word + sum))
|
|
((count = count + 1))
|
|
done
|
|
|
|
# Here we use bc and awk to handle the floating point results of division and proper rounding
|
|
# The avg then gets added to the average array for display later
|
|
avg=$(echo "(${sum}/${count})" | bc -l | awk "${awk_proper_rounding}")
|
|
averages[${line_count}]=${avg}
|
|
|
|
# Now the new string we created is sorted numerically so we can easily find the median
|
|
sorted=$(echo -e ${numbers_string} | sort -n)
|
|
|
|
# Then we find and add the median number to our medians array
|
|
i=0
|
|
for word in ${sorted}
|
|
do
|
|
if [ ${i} == $(((count/2))) ]; then
|
|
medians[${line_count}]=${word}
|
|
break
|
|
fi
|
|
((i = i + 1))
|
|
done
|
|
|
|
# Here we increment our line count so we can properly handle empty files
|
|
((line_count = line_count + 1))
|
|
done
|
|
|
|
# For rows display, we print out the header then one value from averages and count, separated by tabs
|
|
if [ ${is_rows} -eq 1 ]; then
|
|
echo -e "Average\tMedian"
|
|
|
|
count=0
|
|
for word in ${averages[*]}
|
|
do
|
|
echo -e "${averages[count]}\t${medians[count]}"
|
|
((count = count + 1))
|
|
done
|
|
# For cols display, we print a header, then all the contents of average, another header, and the contents of median
|
|
# Takes a little more work to print this one and not have extra tabs left over
|
|
else
|
|
echo -e "Averages:"
|
|
first=1
|
|
for word in ${averages[*]}
|
|
do
|
|
if [ ${first} -eq 1 ]; then
|
|
echo -e -n "${word}"
|
|
first=0
|
|
else
|
|
echo -e -n "\t${word}"
|
|
fi
|
|
((count = count + 1))
|
|
done
|
|
echo
|
|
|
|
echo -e "Medians:"
|
|
first=1
|
|
for word in ${medians[*]}
|
|
do
|
|
if [ ${first} -eq 1 ]; then
|
|
echo -e -n "${word}"
|
|
first=0
|
|
else
|
|
echo -e -n "\t${word}"
|
|
fi
|
|
((count = count + 1))
|
|
done
|
|
echo
|
|
fi
|
|
|
|
# Assuming we make it this far, the trap handler will not have taken care of our temp files, so we do that now
|
|
remove_temp_if_exist
|
|
|
|
# Again, having made it this far the program has completed successfully. Exit with no error.
|
|
exit 0 |