【问题标题】:How to determine if Python script was run via command line?如何确定 Python 脚本是否通过命令行运行?
【发布时间】:2012-04-08 00:22:12
【问题描述】:

背景

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

raw_input("Press enter to close.")

但仅当它不是通过命令行运行时。命令行程序不应该这样做。

问题

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

$ python myscript.py

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

【问题讨论】:

  • 我猜这是Windows?我认为您可以创建一个指向您的程序的链接并更改链接属性以使其在退出时等待。 (至少十二年前当我使用 Windows 时,你可以做这样的事情。)

标签: python command-line


【解决方案1】:

此答案目前特定于 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 字符串,因此如果您之前使用过 Python 版本,请务必将 f 字符串转换为兼容的语法引入了 f 字符串。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      如果您在没有终端的情况下运行它,例如在 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 或终端的参数)之后完成的。

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

      【讨论】:

      • 我认为这可能与您双击的脚本有关,终端打开,但脚本完成后立即关闭。看起来 OP 希望保持终端打开,以便用户可以在关闭之前检查输出。
      • @ThomasK:我认为你是对的,我认为我误读了这个问题。
      • @ThomasK:是的,这是正确的。基本上,如果脚本运行没有问题,它可以关闭解释器调用的 shell 窗口。但如果有问题,我希望 shell 窗口保持打开状态,让用户看到错误。
      • 标准输入可能在交互使用中被重定向,从而错误地使测试失败。我建议用O_NOCTTY 标志打开/dev/tty。如果成功,我们很可能会被交互式终端控制,假设您使用的是 POSIX 系统。
      • 使用sys.stdout.isatty() 而不是stdin 版本通常更有意义。通过使用stdin 版本,手动键入的命令(如yourprogram <inputfile)将被视为非交互式。
      【解决方案4】:

      我想要的在这里得到了回答:Determine if the program is called from a script in 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())
      

      【讨论】:

      • 无法在 cygwin 上运行,因为不支持 psutil。
      • 只给我py.exe。
      【解决方案5】:

      更新为更高版本(例如 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 窗口(文件浏览器)中,选择编辑(菜单)->首选项(项目),然后选择行为(选项卡)->可执行文本文件(部分)->每次询问(收音机)。
      • 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())
      

      【讨论】:

      • 这第一部分(如果 psutil...)效果很好。要判断程序是否在 Windows 中被双击,进程名称似乎是“explorer.exe”,至少在 win10 中是这样。不过需要注意的是:它必须是 Process(os.getpid()).parent().name()),至少在 Python 2.7 中使用 psutil 5.0.1。
      • @SilasGreenback:感谢您在 Win10 下进行的添加。 (正如我所说,我的解决方案适用于 Ubuntu 14。)我可以建议您将答案添加为 Windows 的独立解决方案吗?复制我想要的部分。
      • 我顺便说一下在 PyCharm (Ubuntu) 中运行它时得到名称“java”,包括。在其 Python 控制台中运行它时,虽然在它的集成终端中运行它时是“bash”。
      • @FlorianH:好点——我的回答不包括您从 in IDE 执行 Python 脚本的情况。但是通过一些经验实验,并使用这种通用技术,我建议您可以扩展它以区分任意其他情况。
      【解决方案6】:

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

      在 PyCharm 检查中设置环境变量: How to set environment variables in 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.")
      

      我希望它对你有用。

      【讨论】:

        【解决方案7】:

        this 答案背后的想法,添加 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 的语句以使其更通用或处理不同的默认值。

        【讨论】:

          【解决方案8】:

          好的,我找到并制作的最简单的方法是简单地在命令行中运行程序,即使它是在 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 中重新运行程序,然后退出第一个实例。 希望有帮助! :)

          【讨论】:

            【解决方案9】:

            虽然这不是一个很好的解决方案,但它确实有效(至少在 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
            

            批号来自:Pausing a batch file when double-clicked but not when run from a console window? 多行批量从:DOS: Working with multi-line strings

            【讨论】:

              【解决方案10】:

              如果您运行 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')
              

              【讨论】:

              • 为什么不直接做if 'pythonw.exe' in sys.executable: print('Running in Python IDLE')
              【解决方案11】:

              我的解决方案是使用 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”,程序将永远不会在关闭之前等待用户输入。

              【讨论】:

                【解决方案12】:

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

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

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

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

                【讨论】:

                • --no-pause 不会更简单吗?
                【解决方案13】:

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

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

                【讨论】:

                猜你喜欢
                • 2017-09-13
                • 1970-01-01
                • 2014-05-11
                • 2021-03-20
                • 2014-07-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多