Last active
February 17, 2020 12:23
-
-
Save QNimbus/cd4432ed283d1f97d3cf9065923e566b to your computer and use it in GitHub Desktop.
Script to check if drives are spun down (idle)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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