Skip to content

Instantly share code, notes, and snippets.

@QNimbus
Last active February 17, 2020 12:23
Show Gist options
  • Save QNimbus/cd4432ed283d1f97d3cf9065923e566b to your computer and use it in GitHub Desktop.
Save QNimbus/cd4432ed283d1f97d3cf9065923e566b to your computer and use it in GitHub Desktop.
Script to check if drives are spun down (idle)
#!/usr/bin/env bash
##
## Title: check_drives.sh
## Description: Script to check if drives are spun down (idle)
## Author: B. van wetten
## Created date: 11-02-2020
## Updated date: 17-02-2020
## Version: 0.1.3
## GitHub Gist: https://gist.github.com/QNimbus/cd4432ed283d1f97d3cf9065923e566b
##
## Usage: check_drives.sh
##
## Reference: https://en.wikipedia.org/wiki/S.M.A.R.T.#Known_ATA_S.M.A.R.T._attributes
# Shell utilities
GETOPT=$(which getopt); [[ $? != 0 ]] && echo "Command 'getopt' not found" >&2 && exit 1
SMARTCTL=$(which smartctl); [[ $? != 0 ]] && echo "Command 'smartctl' not found" >&2 && exit 1
# Initialize variables
_ME=$(basename "${0}")
_LOG=check_drives.log
_DEVS=($(smartctl --scan --nocheck standby -d scsi | awk '{print $1}'))
_IGNORE_DEVS=()
# Option strings
SHORT=hm:i:
LONG=help,minutes:,ignore:
#Send stderr & stdout to logfile
exec > >(tee -i $_LOG) 2>&1
# Get command line arguments
if [[ "${#}" -gt 0 ]]
then
getopt -T > /dev/null
if [ $? -eq 4 ]; then
# GNU enhanced getopt is available
_ARGS=$(${GETOPT} --name "$_ME" --long ${LONG} --options ${SHORT} -- "$@")
else
# Original getopt is available (no long option names, no whitespace, no sorting)
_ARGS=$(${GETOPT} ${SHORT} "$@")
fi
if [ $? -ne 0 ]; then
echo "$_ME: usage error (use -h for help)" >&2
exit 2
fi
fi
###############################################################################
# Functions #
###############################################################################
# _print_usage()
#
# Usage:
# _print_usage
#
# Print the program usage information.
_print_usage() {
cat <<HEREDOC
____ _ _ ____ ____ _ _ ___ ____ _ _ _ ____ ____
| |__| |___ | |_/ | \ |__/ | | | |___ [__
|___ | | |___ |___ | \_ ___ |__/ | \ | \/ |___ ___]
Usage:
${_ME} -m 60 -i /dev/da0 /dev/da1
${_ME} -h
Options:
-i Ignore certain devices
-m Run check every x minutes
-h Show this screen
HEREDOC
exit ${1:-0}
}
# _parse_commandline_arguments()
#
# Usage:
# _parse_commandline_arguments
#
# Parses and validates commandline arguments and populates appropriate variables.
_parse_commandline_arguments()
{
while true;
do
case "${1:-}" in
-i|--ignore)
while
if ! [[ -z "${2}" || "${2}" =~ ^-{1,2} ]]
then
_IGNORE_DEVS+=("${2}")
shift 1
fi
! [[ -z "${2}" || "${2}" =~ ^-{1,2} ]]
do
:
done
shift 1
;;
-m|--minutes)
[[ -z "${2}" || "${2}" == *[[:space:]]* || "${2}" == -* ]] && { echo "$_ME: $1 needs a value" >&2; _print_usage 1; }
_set_variable _INTERVAL $(("${2}"*60))
shift 2
;;
\?) echo "$_ME: Unknown option -$1" >&2;
exit 1
;;
:) echo "$_ME: -$1 needs a value" >&2;
exit 1
;;
*) shift
break
;;
esac
done
}
# _validate_parameters()
#
# Usage:
# _validate_parameters
#
# Performs several checks on the supplied command line arguments
_validate_parameters() {
:
}
# _set_variable()
#
# Usage:
# _set_variable variable value
#
# Sets the variable to a value if not already set. Otherwise exits with an error message
_set_variable()
{
local varname="$1"
shift
if [ -z "${!varname:-}" ]; then
eval "$varname=\"$@\""
else
echo "Error: $varname already set"
usage
fi
}
# _print_header()
#
# Usage:
# _print_header
#
# Prints a header to the output
_print_header() {
_DATE=$(date +"%A, %b %d %Y")
printf "########################################################## %*s ###\n" 22 "${_DATE}"
printf "%-10s %12s %20s %35s %4s\n" "Time" "Device" "Model" "State" "Temp"
printf "=%.0s" {1..85}
printf "%b" "\n"
}
# _print_separator()
#
# Usage:
# _print_separator
#
# Prints a line separator to the output
_print_separator() {
printf -- "-%.0s" {1..85}
printf "%b" "\n"
}
###############################################################################
# Main #
###############################################################################
# _main()
#
# Usage:
# _main [<options>] [<arguments>]
#
# Description:
# Entry point for the program, handling basic option parsing and dispatching.
_main() {
# Avoid complex option parsing when only one program option is expected.
if [[ "${@:-}" =~ -h|--help ]]
then
_print_usage
else
_parse_commandline_arguments "$@"
_validate_parameters
# Initialize throwaway array
tmp=()
for DEV in "${_DEVS[@]}"
do
skip=
for IGNORE_DEV in "${_IGNORE_DEVS[@]}"
do
[[ "$DEV" == "$IGNORE_DEV" ]] && { skip=1; break; }
done
[[ -n $skip ]] || tmp+=("$DEV")
done
# Copy array
_DEVS=("${tmp[@]}")
# Remove throwaway array
unset tmp
# Output header
_print_header
# Run once or on interval
while
# All drives idle
idle=0
for DEV in "${_DEVS[@]}"
do
_SMART_OUTPUT=$(${SMARTCTL} --nocheck standby --all "${DEV}")
_RETURN=$?
# Get S.M.A.R.T. data and attributes
_TEMPERTATURE=$(echo "${_SMART_OUTPUT}" | grep "Temperature_Celsius" | grep -o "..$")
_DEVICE_MODEL=$(echo "${_SMART_OUTPUT}" | grep "Device Model" | awk {'print $NF'})
# Check bit 0 - See https://linux.die.net/man/8/smartctl 'Return values'
if [[ $_RETURN -eq 0 ]]
then
_STATE="SPINNING"
# Check bit 1 - See https://linux.die.net/man/8/smartctl 'Return values'
elif [[ $(( $_RETURN & 2**1 )) -ne 0 ]]
then
_STATE="STANDBY"
# Check bit 2 - See https://linux.die.net/man/8/smartctl 'Return values'
elif [[ $(( $_RETURN & 2**2 )) -ne 0 ]]
then
_STATE="S.M.A.R.T. ERROR or UNSUPPORTED"
# Check bit 3 - See https://linux.die.net/man/8/smartctl 'Return values'
elif [[ $(( $_RETURN & 2**3 )) -ne 0 ]]
then
_STATE="DISK FAILING"
else
_STATE="UNKNOWN ERROR"
fi
_TIME=$(date "+%H:%M:%S")
printf "%-10s %12s %20s %35s %4s\n" "${_TIME}" "${DEV}" "${_DEVICE_MODEL:-UNKNOWN}" "${_STATE:-UNKNOWN}" "${_TEMPERTATURE:--}"
done
# Output separator
_print_separator
# This is to emulate a 'do-while' construct in Bash
# If the statement below evaluates to 'true' the loop will continue
[[ ! -z $_INTERVAL ]] && sleep $(("${_INTERVAL}"))
do
:
done
fi
}
# Call `_main` after everything has been defined.
_main "$@"
exit ${exitcode:-0}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment