简体   繁体   English

如何确定 Python 脚本是否通过命令行运行?

[英]How to determine if Python script was run via command line?

Background背景

I would like my Python script to pause before exiting using something similar to:我希望我的 Python 脚本在退出之前暂停,使用类似于:

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 脚本是否是从命令行调用的:

$ python myscript.py

verses double-clicking myscript.py to open it with the default interpreter in the OS?用操作系统中的默认解释器双击myscript.py来打开它?

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:如果您在没有终端的情况下运行它,例如在 Nautilus 中单击“运行”时,您可以检查它是否附加到 tty:

import sys
if sys.stdin and 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.但是,正如 ThomasK 指出的那样,您似乎指的是在程序完成后立即关闭的终端中运行它。 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).立即退出的决定是在它完成了它没有现成的信息(传递给正在执行的 shell 或终端的参数)之后完成的。

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 )?您是否考虑过在脚本中添加命令行参数(想想--interactive )?

What I wanted was answered here: Determine if the program is called from a script in Python我想要的在这里得到了回答: 确定程序是否从 Python 中的脚本调用

You can just determine between "python" and "bash".您可以在“python”和“bash”之间确定。 This was already answered I think, but you can keep it short as well.我认为这已经得到了回答,但你也可以保持简短。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import psutil
import os

ppid = os.getppid() # Get parent process id
print(psutil.Process(ppid).name())

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.如果您运行 python IDLE,则“pythonw.exe”用于运行编码,而当您运行命令行时,“python.exe”用于运行编码。 The python folder path can vary so you have to revert the path to the python folder. python 文件夹路径可能会有所不同,因此您必须将路径还原为 python 文件夹。 m = '\\\\' and m = m[0] is to get m to be '\\' because of escaping. m = '\\\\' 和 m = m[0] 是因为转义而使 m 成为 '\\' 。

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')

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.例如,在 OS X 上,当您双击一个.py文件并使用“Python Launcher”进行调整时,它会在终端中运行,与您手动执行时相同。

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)虽然它可能有其他问题,但您可以将脚本与py2exePlatypus之类的东西打包,然后您可以让双击图标运行特定的代码来区分( import mycode; mycode.main(gui = True) for例子)

Update for later versions (eg Python 3.6 on Ubuntu 16.04) : The statement to get the name has changed to psutil.Process(os.getpid()).parent().name()更高版本的更新(例如 Ubuntu 16.04 上的 Python 3.6) :获取名称的语句已更改为psutil.Process(os.getpid()).parent().name()


I believe this CAN be done.我相信这是可以做到的。 At least, here is how I got it working in Python 2.7 under Ubuntu 14.04:至少,这是我如何在 Ubuntu 14.04 下的 Python 2.7 中工作的:

#!/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:请注意,在带有 Gnome 桌面(又名 Nautilus)的 Ubuntu 14 中,您可能需要这样做:

  • from a Nautilus window (the file browser), select Edit(menu)->Preferences(item) then Behavior(tab)->Executable Text Files(section)->Ask Each Time(radio).从 Nautilus 窗口(文件浏览器)中,选择 Edit(menu)->Preferences(item) 然后 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) chmod 你的脚本是可执行的,或者 -- 从 Nautilus 窗口(文件浏览器) -- 右键单击​​文件-> 属性(项目)然后权限(选项卡)-> 执行:允许将文件作为程序执行(复选框)
  • 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;现在从 bash 提示符尝试; you should NOT see the prompt.你不应该看到提示。

To see how this works, you can fiddle with this (based on the answer by from @EduardoIvanec):要了解它是如何工作的,您可以摆弄这个(基于@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())

My solution was to create command line scripts using setuptools.我的解决方案是使用 setuptools 创建命令行脚本。 Here are a the relevant parts of myScript.py:以下是 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.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.现在,如果我使用 Python 解释器(在 Windows 上)打开 myScript.py,如果发生错误,控制台窗口会等待用户按 Enter。 On the command line, if I run 'myScript', the program will never wait for user input before closing.在命令行上,如果我运行“myScript”,程序将永远不会在关闭之前等待用户输入。

From the idea behind this answer, adding for Win10 compatibility (Ripped from Python 2.7 script; modify as needed):根据这个答案背后的想法,添加 Win10 兼容性(从 Python 2.7 脚本中提取;根据需要进行修改):

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.您可以添加许多类似 switch 的语句以使其更通用或处理不同的默认值。

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.您应该在脚本中添加一个--pause参数,该参数在最后提示输入键。

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.当手动从命令行调用脚本时,用户可以根据需要添加--pause ,但默认情况下不会有任何等待。

When the script is launched from an icon, the arguments in the icon should include the --pause , so that there is a wait.从图标启动脚本时,图标中的参数应包含--pause ,以便等待。 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.不幸的是,您需要记录此选项的使用,以便用户知道在创建图标时需要添加它,或者在您的脚本中提供适用于您的目标操作系统的图标创建功能。

Although this isn't a very good solution, it does work (in windows at least).虽然这不是一个很好的解决方案,但它确实有效(至少在 Windows 中)。

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批量多行来自: DOS:使用多行字符串

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.好的,我找到并制作的最简单的方法是简单地在命令行中运行程序,即使它是在 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.将此添加到脚本的底部,一旦从 IDLE 或命令提示符运行,它将创建一个文件,在 CMD 中重新运行程序,然后退出第一个实例。 Hope that helps!希望有帮助! :) :)

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.我也有这个问题,对我来说,最好的解决方案是在我的 IDE (PyCharm) 中设置一个环境变量并检查该变量是否存在以了解脚本是通过命令行还是通过 IDE 执行的。

To set an environment variable in PyCharm check: How to set environment variables in PyCharm?要在 PyCharm 中设置环境变量检查: 如何在 PyCharm 中设置环境变量?

Example code (environment variable: RUNNING_PYCHARM = True):示例代码(环境变量: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.我希望这个对你有用。

Based on existing solutions and using sets:基于现有解决方案和使用集合:

import psutil

def running_interactively():
    """Return True if any of our parent processes is a known shell."""
    shells = {"cmd.exe", "bash.exe", "powershell.exe", "WindowsTerminal.exe"}
    parent_names = {parent.name() for parent in psutil.Process().parents()}
    # print(parent_names)
    # print(f"Shell in parents? {shells & parent_names}")
    return bool(shells & parent_names)


if not running_interactively():
    input("\nPress ENTER to continue.")

This answer is currently specific to Windows, but it can be reconfigured to work with other operating systems in theory.此答案当前特定于 Windows,但理论上可以重新配置以与其他操作系统一起使用。 Rather than installing psutil module like most of these answers recommend, you can make use of the subprocess module and the Windows tasklist command to explicitly get the name of the parent process of your Python program.您可以使用subprocess模块和 Windows tasklist命令来显式获取 Python 程序的父进程的名称,而不是像大多数这些答案推荐的那样安装psutil模块。

import os
import subprocess
shells = {"bash.exe", "cmd.exe", "powershell.exe", "WindowsTerminal.exe"}
# These are standard examples, but it can also be used to detect:
# - Nested python.exe processes (IDLE, etc.)
# - IDEs used to develop your program (IPython, Eclipse, PyCharm, etc.)
# - Other operating system dependent shells

s = subprocess.check_output(["tasklist", "/v", "/fo", "csv", "/nh", "/fi", f"PID eq {os.getppid()}"])
# Execute tasklist command to get the verbose info without the header (/nh) of a single process in CSV format (/fo csv)
# Such that its PID is equal to os.getppid()

entry = s.decode("utf-8").strip().strip('"').split('","')
# Decode from bytes to str, remove end whitespace and quotations from CSV format
# And split along the quote delimited commas
# This process may differ and require adjustment when used for an OS other than Windows

condition = entry and entry[0] in shells
# Check first that entry is not an empty sequence, meaning the process has already ended
# If it still exists, check if the first element -- the executable -- exists as an element of the set of executables you're looking for

I hope this is helpful for anyone looking for an answer to this problem while minimizing the number of dependencies you'd need.我希望这对任何寻求解决此问题的人有所帮助,同时最大限度地减少您需要的依赖项数量。

This was tested in Python 3.8 and uses an f-string in the subprocess.check_output line of the code, so please be sure to convert the f-string to a compatible syntax if you're working with a version of Python before f-strings were introduced.这在 Python 3.8 中进行了测试,并在代码的subprocess.check_output行中使用了 f-string,因此如果您使用 f-strings 之前的 Python 版本,请务必将 f-string 转换为兼容的语法被介绍了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM