So I'm relatively new to programming in general, and I've finally strapped in and started learning Python. This is my first significant project using it and I'm trying to build good habits with layout of my code, readability, comments, when to make functions, when not to etc.
The problem I was solving was that I have about 20 hard drives that I wanted to run the same tests on using smartmontools on my ubuntu server system and make nice files detailing their status/health before I put them into a cluster system.
Some specific concerns of mine are:
Overall workflow. Did I implement use of my
main()
function correctly? I figured some functions that I could use outside of this script I left outside ofmain()
so I could possibly call them in other scripts. But left ones specific tomain()
inside.Commenting. More? Less? This is just for me for now, but would you be able to see whats going on?
I do lots of string manipulation and it looks sorta messy to me at a glance. Are there cleaner/easier ways to do this?
Any other critiques are welcome!
#!/usr/bin/python
##----------------
##Script Name: hd-diag
##Description of Script: Runs diagnostics on harddrives and writes results to
##file
##Date: 2013.07.09-11:48
##Version: 0.1.0
##Requirements: python2, smartmontools, posix environment
##TODO: add support for other incorrect input in YN questions
##----------------
import os
import sys
import subprocess
import getopt
def unix_call(cmd):
'''(str) -> str
Calls unix terminal process cmd and returns stdout upon process completion.
'''
output = []
global errordic
p = subprocess.Popen(
cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for line in iter(p.stdout.readline, ''):
output.append(line.strip('\n'))
if p.stderr:
for line in iter(p.stderr.readline, ''):
errordic['unix_call'] = line.strip('\n')
return '\n'.join(output).strip("'[]'")
def test_call(cmd):
p = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE)
test_results_raw = p.stdout.read()
return test_results_raw
def write_str_to_file(string, filenamepath):
'''(str) -> string in file
Writes the contents of str to file as array. Assumes current directory
if just filename is given.
Full path must be given.
'''
def do_write(string, destination):
to_file = open(destination, 'w')
for item in string:
to_file.write(str(item))
to_file.close()
if '/' not in filenamepath:
cur_dir = os.getcwd()
dest = cur_dir + '/' + filenamepath
do_write(string, dest)
elif '/' in filenamepath:
if filenamepath[0] != '/':
dest = '/' + filenamepath
do_write(string, dest)
elif filenamepath[0] == '/':
do_write(string, filenamepath)
#set global variables
errordic = {}
driveloc = ''
outputloc = ''
def main():
global driveloc, outputloc, errordic
def getargs(argv):
global driveloc, outputloc, errordic
try:
opts, args = getopt.getopt(
argv, 'hd:o:', ['driveloc=', 'outputloc='])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
usage()
sys.exit()
elif opt in ('-d', '--driveloc'):
driveloc = arg
elif opt in ('-o', '--outputloc'):
outputloc = arg
def usage():
print "hd-diag.py -d <hdpath> -o <outputloc>"
getargs(sys.argv[1:])
print "Selected drive is: ", driveloc
if outputloc != '':
if outputloc[-1] != '/':
outputloc = outputloc + '/'
print "File output location is: ", outputloc
elif outputloc[-1] == '/':
print "File output location is: ", outputloc
elif outputloc == '':
print "No output location selected. Using current directory."
outputloc = os.getcwd() + '/'
print "File output location is: ", outputloc
#TODO: detect here if drive is ata or sata and print
#make sure harddrive is unmounted
while unix_call("mount | grep '%s'" % driveloc):
unix_call("sudo umount %s" % driveloc)
if unix_call("mount | grep '%s'" % driveloc):
print"Can't unmount %s" % driveloc
print"Err:", errordic['unix_call']
print"Exiting."
sys.exit(1)
else:
print"%s is not mounted, continuing" % driveloc
#check if drive supports SMART capability, enabled if need be
if 'Available' in unix_call(
"sudo smartctl -i %s | grep 'SMART support is:'" % driveloc):
print "SMART support is available on %s..." % driveloc
if 'Enabled' in unix_call(
"sudo smartctl -i %s | grep 'SMART support is:'" % driveloc):
print "...and enabled"
else:
print "...but disabled"
while not 'Enabled' in unix_call("smartctl -i %s" % driveloc):
enableYN = raw_input(
"Would you like to enable SMART support? [y/n]:")
if enableYN in ('yYes'):
unix_call("sudo smartctl -s %s" % driveloc)
elif enableYN in ('nNo'):
print "Script cannot continue without SMART capability "
"enabled"
print "Exiting."
sys.exit(1)
else:
print 'Sorry, but "%s" is not a valid input.' % enableYN
#run desired tests on harddrive
#get estimated time for extended test
est_ttime = unix_call(
"sudo smartctl -c %s | grep -A 1 'Extended self-test'" % driveloc)
est_ttime = est_ttime[est_ttime.rfind('('):].replace('( ', '(').rstrip('.')
testYN = raw_input("The estimated time for the test is %s, would you "
"like to proceed?[y/n]: " % est_ttime)
#run test
if testYN in ('Yy'):
console_tstart = test_call("sudo smartctl -t long %s" % driveloc)
print console_tstart
elif testYN in ('Nn'):
print 'Test cancelled. Exiting.'
sys.exit(1)
#prompt for continue
contCE = raw_input(
"\nThe test is running. After the estimated time listed "
"above has passed, press 'c' to coninue or type 'exit' to "
"exit.[c/exit]: ")
#extract test result data
if contCE in 'cC':
console_tinfo = test_call(
"sudo smartctl -i %s" % driveloc).split('\n', 2)
console_thealth = test_call(
"sudo smartctl -H %s" % driveloc).split('\n', 2)
console_tresults = test_call(
"sudo smartctl -l selftest %s" % driveloc).split('\n', 2)
console_terror = test_call(
"sudo smartctl -l error %s" % driveloc).split('\n', 2)
log_tinfo = str(console_tinfo[2]).lstrip('\n')
log_thealth = str(console_thealth[2]).lstrip('\n')
log_tresults = str(console_tresults[2]).lstrip('\n')
log_terror = str(console_terror[2]).lstrip('\n')
print log_tinfo + log_thealth + log_tresults + log_terror
elif contCE in 'Exitexit':
print 'Test cancelled. Exiting.'
sys.exit(1)
#write output to file
#extract brand, model, serial no., and size for file naming
deviceinfo = [i.strip('\n').lstrip() for i in test_call(
"sudo smartctl -i %s" % driveloc).split(':')]
devicebrand = deviceinfo[2].split(' ')[0]
devicemodel = deviceinfo[3].split(' ')[0].split('\n')[0]
deviceserial = deviceinfo[4].split(' ')[0].split('\n')[0]
devicesize = deviceinfo[6].split(' ')[2].strip('[')
filename = '-'.join([devicebrand, devicemodel, deviceserial, devicesize])
#write data to file
finalwrite = outputloc + filename
write_confirm = raw_input(
"The data is ready to be written to:\n\t%s\n\n"
"would you like to continue?[y/n]: " % finalwrite)
if write_confirm in 'Yy':
#TODO test for already existing file
write_str_to_file(
log_tinfo + log_thealth + log_tresults + log_terror, finalwrite)
print "File writen. Test complete. Exiting"
sys.exit(1)
elif write_confirm in 'Nn':
print "File write canceled. Exiting"
sys.exit(1)
#main
if __name__ == '__main__':
main()