Search code examples
pythonsplitsubprocesspyinstallerstrsplit

Why is this subprocess.check_output line crashing my script?


I have a script that works fine when in a .pyw, but when converted to an .exe, (EDIT: actually, when I use pyinstaller with the arguments -w or --windowed or --noconsole it doesn't work, but without them it works) I've found that this one single line seems to crash the program:

firstplan = subprocess.check_output(["powercfg", "-list"], shell=True ).split('\n')[3]

Does anybody have any idea why? If I comment it out the program doesn't crash. I have two other very similar lines.

EDIT:

Maybe it'd be a good idea to put the script here...

from __future__ import print_function
import os
# os.system('cls')
import psutil
import subprocess

loop = 1

while loop == (1):

    CPUload = (psutil.cpu_percent(interval=4))      # CPU load
    RAMload = (psutil.virtual_memory().percent)     # RAM load

    # os.system('cls')

    print("CPU Load: ", end="")         # Display CPU Load:
    print(CPUload, "%")                 # Display CPUload
    print("RAM Load: ", end="")         # Display CPU Load:
    print(str(RAMload) + " %")          # Display RAMload

    firstplan = subprocess.check_output(["powercfg", "-list"], shell=True ).split('\n')[3]      # Selects a line
    secondplan = subprocess.check_output(["powercfg", "-list"], shell=True ).split('\n')[4]
    thirdplan = subprocess.check_output(["powercfg", "-list"], shell=True ).split('\n')[5]

    firstplanID = ((firstplan.split(": "))[1].split("  (")[0])      # Extract XplanID from Xplan
    secondplanID = ((secondplan.split(": "))[1].split("  (")[0])
    thirdplanID = ((thirdplan.split(": "))[1].split("  (")[0])

    activeplan = subprocess.check_output(["powercfg", "/getactivescheme"])      # Find the currently active plan
    activeplanNAME = ((activeplan.split("("))[1].split(")")[0])     # Extract activeplanNAME from activeplan

    firstplanNAME = ((firstplan.split("("))[1].split(")")[0])       # Extract XplanNAME from Xplan
    secondplanNAME = ((secondplan.split("("))[1].split(")")[0])
    thirdplanNAME = ((thirdplan.split("("))[1].split(")")[0])


    if "High performance" in firstplanNAME:         # Identify which plan is High performance
        HighPerformance = firstplanNAME
        HighPerformanceID = firstplanID

    if "High performance" in secondplanNAME:
        HighPerformance = secondplanNAME
        HighPerformanceID = secondplanID

    if "High performance" in thirdplanNAME:
        HighPerformance = thirdplanNAME
        HighPerformanceID = thirdplanID

    if "Power saver" in firstplanNAME:              # Identify which plan is Power saver
        PowerSaver = firstplanNAME
        PowerSaverID = firstplanID

    if "Power saver" in secondplanNAME:
        PowerSaver = secondplanNAME
        PowerSaverID = secondplanID

    if "Power saver" in thirdplanNAME:
        PowerSaver = thirdplanNAME  
        PowerSaverID = thirdplanID


    if activeplanNAME == PowerSaver:            # Checks current plan name
        print("Active plan: Power saver")
    else:
        if activeplanNAME == HighPerformance:
            print("Active plan: High Performance")
        else: 
            subprocess.check_output(["powercfg", "/s", HighPerformanceID])          


    if CPUload < 44:    
        if RAMload > 90:
            if activeplanNAME == PowerSaver:
                subprocess.check_output(["powercfg", "/s", HighPerformanceID])
                print("Switching to High Performance by RAM load...")       

    if CPUload < 44:    
        if RAMload < 90:
            if activeplanNAME == HighPerformance:
                subprocess.check_output(["powercfg", "/s", PowerSaverID])
                print("Switching to Power saver...")                    

    if CPUload > 55:
        if activeplanNAME == PowerSaver:
            subprocess.check_output(["powercfg", "/s", HighPerformanceID])
            print("Switching to High Performance...")

The troubled lines are lines 21-23.

For further info, scroll down to comments and answers.


Solution

  • I'm not sure if this will solve your problem, but here is a refactoring which addresses the problems which were pointed out in comments, as well as some other issues with your code.

    • Don't use a loop variable. (It was unused anyway.)
    • Don't run the same subprocess three times.
    • Avoid the gratuitous shell=True
    • Prefer /list over -list for consistency and possibly correctness.

    I have removed your comments and inlined comments of mine explaining what exactly was changed.

    # Only necessary in Python 2, you really should be using Python 3 now
    #from __future__ import print_function
    # Only used in os.system('cls') which was commented out (for good reasons I presume)
    #import os
    import psutil
    import subprocess
    from time import sleep # see below
    
    # Simply loop forever; break when done
    while True:
        # Remove gratuitous parentheses
        CPUload = psutil.cpu_percent(interval=4)
        RAMload = psutil.virtual_memory().percent
    
        # Use .format() to inline a string
        print("CPU Load: {}%".format(CPUload)))
        print("RAM Load: {}%".format(RAMload))
    
        # Only run subprocess once; use /list pro -list; don't use shell=True
        pcfg = subprocess.check_output(["powercfg", "/list"], shell=False).split('\n')
        # Additional refactoring below ########
        firstplan = pcfg[3]
        secondplan = pcfg[4]
        thirdplan = pcfg[5]
    
        # Get rid of wacky parentheses    
        firstplanID = firstplan.split(": ")[1].split("  (")[0]
        secondplanID = secondplan.split(": ")[1].split("  (")[0]
        thirdplanID = thirdplan.split(": ")[1].split("  (")[0]
    
        activeplan = subprocess.check_output(["powercfg", "/getactivescheme"])
        # Get rid of wacky parentheses
        activeplanNAME = activeplan.split("(")[1].split(")")[0]    
        firstplanNAME = firstplan.split("(")[1].split(")")[0]
        secondplanNAME = secondplan.split("(")[1].split(")")[0]
        thirdplanNAME = thirdplan.split("(")[1].split(")")[0]
    
        if "High performance" in firstplanNAME:
            HighPerformance = firstplanNAME
            HighPerformanceID = firstplanID
    
        if "High performance" in secondplanNAME:
            HighPerformance = secondplanNAME
            HighPerformanceID = secondplanID
    
        if "High performance" in thirdplanNAME:
            HighPerformance = thirdplanNAME
            HighPerformanceID = thirdplanID
    
        if "Power saver" in firstplanNAME:
            PowerSaver = firstplanNAME
            PowerSaverID = firstplanID
    
        if "Power saver" in secondplanNAME:
            PowerSaver = secondplanNAME
            PowerSaverID = secondplanID
    
        if "Power saver" in thirdplanNAME:
            PowerSaver = thirdplanNAME  
            PowerSaverID = thirdplanID
    
        # Additional refactoring ends    
    
        if activeplanNAME == PowerSaver:
            print("Active plan: Power saver")
        # prefer if / elif / else over nested if
        elif activeplanNAME == HighPerformance:
            print("Active plan: High Performance")
        else:
            # What's this supposed to do? You are capturing, then discarding the output.
            # Perhaps you are looking for subprocess.check_call()?
            subprocess.check_output(["powercfg", "/s", HighPerformanceID])          
    
        if CPUload < 44:    
            # Combine conditions rather than nesting conditionals
            if RAMload > 90 and activeplanNAME == PowerSaver:
                # subprocess.check_call() here too?
                subprocess.check_output(["powercfg", "/s", HighPerformanceID])
                print("Switching to High Performance by RAM load...")       
    
            # Don't check if CPUload < 44: again
            # Instead, just stay within this indented block
            # Combine conditions
            elif RAMload < 90 and activeplanNAME == HighPerformance:
                # subprocess.check_call() here too?
                subprocess.check_output(["powercfg", "/s", PowerSaverID])
                print("Switching to Power saver...")                    
    
            # What if RAMload == 90?
    
        # Combine conditions
        if CPUload > 55 and activeplanNAME == PowerSaver:
            # subprocess.check_call() here too?
            subprocess.check_output(["powercfg", "/s", HighPerformanceID])
            print("Switching to High Performance...")
    
        # Maybe sleep between iterations?
        #sleep(1)
    

    The script currently runs a rather tight loop, you might want to uncomment the last line.

    There is still a lot of repetitive code. You might want to consider further refactoring to collect the three plans into an array where each object is a dict with member names identifying different properties you have extracted.

        # Additional refactoring below ########
        activeplan = subprocess.check_output(["powercfg", "/getactivescheme"])
        activeplanNAME = activeplan.split("(")[1].split(")")[0]    
    
        plan = []
        for idx in range(3):
            raw = pcfg[3+idx]
            thisplan = {'raw': raw}
            thisplan['id'] = raw.split(": ")[1].split("  (")[0]
            thisplan['name'] = raw.split("(")[1].split(")")[0]
    
            if "High performance" in thisplan['name']:
                HighPerformance = thisplan['name']
                HighPerformanceID = thisplan['id']    
    
            if "Power saver" in thisplan['name']:
                PowerSaver = thisplan['name']
                PowerSaverID = thisplan['id']
    
            plan[idx] = thisplan    
    

    Now you could actually change the HighPerformance and PowerSaver variables to just remember the idx and then if you want to, you can pull out the name from the list of dicts with plan[PowerSaverIdx]['name'] and ID with plan[PowerSaverIdx]['id'].