How to determine if Python script was run via comm

2019-02-11 13:33发布

问题:

Background

I would like my Python script to pause before exiting using something similar to:

raw_input("Press enter to close.")

but only if it is NOT run via command line. Command line programs shouldn't behave this way.

Question

Is there a way to determine if my Python script was invoked from the command line:

$ python myscript.py

verses double-clicking myscript.py to open it with the default interpreter in the OS?

回答1:

I don't think there's any reliable way to detect this (especially in a cross-platform manner). For example on OS X, when you double-click a .py file and it tuns with "Python Launcher", it runs in a terminal, identically to if you execute it manually.

Although it may have other issues, you could package the script up with something like py2exe or Platypus, then you can have the double-clickable icon run a specific bit of code to differentiate (import mycode; mycode.main(gui = True) for example)



回答2:

If you're running it without a terminal, as when you click on "Run" in Nautilus, you can just check if it's attached to a tty:

import sys
if sys.stdin.isatty():
    # running interactively
    print "running interactively"
else:
    with open('output','w') as f:
        f.write("running in the background!\n")

But, as ThomasK points out, you seem to be referring to running it in a terminal that closes just after the program finishes. I think there's no way to do what you want without a workaround; the program is running in a regular shell and attached to a terminal. The decision of exiting immediately is done just after it finishes with information it doesn't have readily available (the parameters passed to the executing shell or terminal).

You could go about examining the parent process information and detecting differences between the two kinds of invocations, but it's probably not worth it in most cases. Have you considered adding a command line parameter to your script (think --interactive)?



回答3:

If you run python IDLE then "pythonw.exe" is being used to run coding while when you run the command line "python.exe" is used to run coding. The python folder path can vary so you have to revert the path to the python folder. m = '\\' and m = m[0] is to get m to be '\' because of escaping.

import sys
a = sys.executable
m = '\\'
m = m[0]
while True:
    b = len(a)
    c = a[(b - 1)]
    if c == m:
        break
    a = a[:(b - 1)]
if sys.executable == a + 'pythonw.exe':
    print('Running in Python IDLE')
else:
    print('Running in Command line')


回答4:

This is typically done manually/, I don't think there is an automatic way to do it that works for every case.

You should add a --pause argument to your script that does the prompt for a key at the end.

When the script is invoked from a command line by hand, then the user can add --pause if desired, but by default there won't be any wait.

When the script is launched from an icon, the arguments in the icon should include the --pause, so that there is a wait. Unfortunately you will need to either document the use of this option so that the user knows that it needs to be added when creating an icon, or else, provide an icon creation function in your script that works for your target OS.



回答5:

My solution was to create command line scripts using setuptools. Here are a the relevant parts of myScript.py:

def main(pause_on_error=False):
    if run():
        print("we're good!")
    else:
        print("an error occurred!")
        if pause_on_error:
            raw_input("\nPress Enter to close.")
        sys.exit(1)

def run():
    pass  # run the program here
    return False  # or True if program runs successfully

if __name__ == '__main__':
    main(pause_on_error=True)

And the relevant parts of setup.py:

setup(
entry_points={
        'console_scripts': [
            'myScript = main:main',
        ]
    },
)

Now if I open myScript.py with the Python interpreter (on Windows), the console window waits for the user to press enter if an error occurs. On the command line, if I run 'myScript', the program will never wait for user input before closing.



回答6:

I believe this CAN be done. At least, here is how I got it working in Python 2.7 under Ubuntu 14.04:

#!/usr/bin/env python
import os, psutil

# do stuff here

if psutil.Process(os.getpid()).parent.name == 'gnome-terminal':
    raw_input("Press enter to close...")

Note that -- in Ubuntu 14 with the Gnome desktop (aka Nautilus) -- you might need to do this:

  • from a Nautilus window (the file browser), select Edit(menu)->Preferences(item) then Behavior(tab)->Executable Text Files(section)->Ask Each Time(radio).
  • chmod your script to be executable, or -- from a Nautilus window (the file browser) -- right click on the file->Properties(item) then Permissions(tab)->Execute:Allow executing file as program(checkbox)
  • double-click your file. If you select "Run in Terminal", you should see the "Type enter to close..." prompt.
  • now try from a bash prompt; you should NOT see the prompt.

To see how this works, you can fiddle with this (based on the answer by from @EduardoIvanec):

#!/usr/bin/env python
import os
import sys
import psutil

def parent_list(proc=None, indent=0):
    if not proc:
        proc = psutil.Process(os.getpid())
    pid = proc.pid
    name = proc.name
    pad = " " * indent
    s = "{0}{1:5d} {2:s}".format(pad, pid, name)
    parent = proc.parent
    if parent:
        s += "\n" + parent_list(parent, indent+1)
    return s

def invoked_from_bash_cmdline():
    return psutil.Process(os.getpid()).parent.name == "bash"

def invoked_as_run_in_terminal():
    return psutil.Process(os.getpid()).parent.name == "gnome-terminal"

def invoked_as_run():
    return psutil.Process(os.getpid()).parent.name == "init"


if sys.stdin.isatty():
    print "running interactively"
    print parent_list()
    if invoked_as_run_in_terminal():
        raw_input("Type enter to close...")

else:
    with open('output','w') as f:
        f.write("running in the background!\n")
        f.write("parent list:\n")
        f.write(parent_list())


回答7:

Although this isn't a very good solution, it does work (in windows at least).

You could create a batch file with the following contents:

@echo off
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
start <location of python script>
if defined DOUBLECLICKED pause

If you want to be able to do this with a single file, you could try the following:

@echo off
setlocal EnableDelayedExpansion
set LF=^


::  The 2 empty lines are necessary
for %%x in (%cmdcmdline%) do if /i "%%~x"=="/c" set DOUBLECLICKED=1
echo print("first line of python script") %LF% print("second and so on") > %temp%/pyscript.py
start /wait console_title pyscript.py
del %temp%/pyscript.py
if defined DOUBLECLICKED pause

Batch code from: Pausing a batch file when double-clicked but not when run from a console window? Multi-line in batch from: DOS: Working with multi-line strings



回答8:

Okay, the easiest way I found and made was to simply run the program in the command line, even if it was ran in the Python IDLE.

exist = lambda x: os.path.exists(x)    ## Doesn't matter

if __name__ == '__main__':

    fname = "SomeRandomFileName"    ## Random default file name

    if exist(fname)==False:         ## exist() is a pre-defined lambda function
        jot(fname)                  ## jot() is a function that creates a blank file
        os.system('start YourProgram.py')    ## << Insert your program name here
        os.system('exit'); sys.exit()   ## Exits current shell (Either IDLE or CMD)

    os.system('color a')            ## Makes it look cool! :p
    main()                          ## Runs your code
    os.system("del %s" % fname)     ## Deletes file name for next time

Add this to the bottom of your script and once ran from either IDLE or Command Prompt, it will create a file, re-run the program in the CMD, and exits the first instance. Hope that helps! :)



回答9:

From the idea behind this answer, adding for Win10 compatibility (Ripped from Python 2.7 script; modify as needed):

import os, psutil
status = 1
if __name__ =="__main__":
    status = MainFunc(args)
    args = sys.argv
    running_windowed = False
    running_from = psutil.Process(os.getpid()).parent().name()
    if running_from == 'explorer.exe':
        args.append([DEFAULT OR DOUBLE CLICK ARGS HERE])
        running_windowed = True
    if running_windowed:
        print('Completed. Exit status of {}'.format(status))
        ready = raw_input('Press Enter To Close')
    sys.exit(status)

There is a number of switch like statements you could add to be more universal or handle different defaults.



回答10:

I also had that question and, for me, the best solution is to set an environment variable in my IDE (PyCharm) and check if that variable exists to know if the script is being executed either via the command line or via the IDE.

To set an environment variable in PyCharm check: How to set environment variables in PyCharm?

Example code (environment variable: RUNNING_PYCHARM = True):

import os

# The script is being executed via the command line
if not("RUNNING_PYCHARM" in os.environ):
    raw_input("Press enter to close.")

I hope it works for you.