繁体   English   中英

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

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

背景

我希望我的 Python 脚本在退出之前暂停,使用类似于:

raw_input("Press enter to close.")

但前提是它不是通过命令行运行的。 命令行程序不应该以这种方式运行。

问题

有没有办法确定我的 Python 脚本是否是从命令行调用的:

$ python myscript.py

用操作系统中的默认解释器双击myscript.py来打开它?

如果您在没有终端的情况下运行它,例如在 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")

但是,正如 ThomasK 指出的那样,您似乎指的是在程序完成后立即关闭的终端中运行它。 我认为没有解决方法就无法做你想做的事。 该程序在常规外壳中运行并连接到终端。 立即退出的决定是在它完成了它没有现成的信息(传递给正在执行的 shell 或终端的参数)之后完成的。

您可以检查父进程信息并检测两种调用之间的差异,但在大多数情况下可能不值得。 您是否考虑过在脚本中添加命令行参数(想想--interactive )?

我想要的在这里得到了回答: 确定程序是否从 Python 中的脚本调用

您可以在“python”和“bash”之间确定。 我认为这已经得到了回答,但你也可以保持简短。

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

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

如果您运行 python IDLE,则“pythonw.exe”用于运行编码,而当您运行命令行时,“python.exe”用于运行编码。 python 文件夹路径可能会有所不同,因此您必须将路径还原为 python 文件夹。 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')

我认为没有任何可靠的方法可以检测到这一点(尤其是跨平台方式)。 例如,在 OS X 上,当您双击一个.py文件并使用“Python Launcher”进行调整时,它会在终端中运行,与您手动执行时相同。

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

更高版本的更新(例如 Ubuntu 16.04 上的 Python 3.6) :获取名称的语句已更改为psutil.Process(os.getpid()).parent().name()


我相信这是可以做到的。 至少,这是我如何在 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...")

请注意,在带有 Gnome 桌面(又名 Nautilus)的 Ubuntu 14 中,您可能需要这样做:

  • 从 Nautilus 窗口(文件浏览器)中,选择 Edit(menu)->Preferences(item) 然后 Behavior(tab)->Executable Text Files(section)->Ask Each Time(radio)。
  • chmod 你的脚本是可执行的,或者 -- 从 Nautilus 窗口(文件浏览器) -- 右键单击​​文件-> 属性(项目)然后权限(选项卡)-> 执行:允许将文件作为程序执行(复选框)
  • 双击您的文件。 如果您选择“在终端中运行”,您应该会看到“输入以关闭...”提示。
  • 现在从 bash 提示符尝试; 你不应该看到提示。

要了解它是如何工作的,您可以摆弄这个(基于@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())

我的解决方案是使用 setuptools 创建命令行脚本。 以下是 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)

以及 setup.py 的相关部分:

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

现在,如果我使用 Python 解释器(在 Windows 上)打开 myScript.py,如果发生错误,控制台窗口会等待用户按 Enter。 在命令行上,如果我运行“myScript”,程序将永远不会在关闭之前等待用户输入。

根据这个答案背后的想法,添加 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)

您可以添加许多类似 switch 的语句以使其更通用或处理不同的默认值。

这通常是手动完成的/,我认为没有一种适用于每种情况的自动方法。

您应该在脚本中添加一个--pause参数,该参数在最后提示输入键。

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

从图标启动脚本时,图标中的参数应包含--pause ,以便等待。 不幸的是,您需要记录此选项的使用,以便用户知道在创建图标时需要添加它,或者在您的脚本中提供适用于您的目标操作系统的图标创建功能。

虽然这不是一个很好的解决方案,但它确实有效(至少在 Windows 中)。

您可以创建一个包含以下内容的批处理文件:

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

如果您希望能够使用单个文件执行此操作,您可以尝试以下操作:

@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

批处理代码来自:双击时暂停批处理文件,但从控制台窗口运行时不暂停? 批量多行来自: DOS:使用多行字符串

好的,我找到并制作的最简单的方法是简单地在命令行中运行程序,即使它是在 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

将此添加到脚本的底部,一旦从 IDLE 或命令提示符运行,它将创建一个文件,在 CMD 中重新运行程序,然后退出第一个实例。 希望有帮助! :)

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

要在 PyCharm 中设置环境变量检查: 如何在 PyCharm 中设置环境变量?

示例代码(环境变量: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.")

我希望这个对你有用。

基于现有解决方案和使用集合:

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.")

此答案当前特定于 Windows,但理论上可以重新配置以与其他操作系统一起使用。 您可以使用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

我希望这对任何寻求解决此问题的人有所帮助,同时最大限度地减少您需要的依赖项数量。

这在 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