#!/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