将自身安装到另一个位置并从那里运行的脚本

Script that installs itself to another location and runs from there

我正在尝试创建一个可以做两件事的 Python 脚本:

我尝试了 subprocess.runsubprocess.Popenshellclose_fds 和其他选项的多种组合(甚至尝试过 nohup),但是由于我不太了解进程生成的工作原理,因此我似乎没有使用正确的进程。

当我使用 install 参数时,我正在寻找的是看到 "Installing..." 就是这样,新进程应该是 运行ning 在后台分离,我的 shell 准备好了。但是我看到的是子进程仍然附加并且我的终端忙于输出 "运行ning..." 就在安装消息之后。

应该怎么做?

import subprocess
import sys
import time
import os
    
def installAndRun():
    print('Installing...')
    scriptPath = os.path.realpath(__file__)
    scriptName = (__file__.split('/')[-1] if '/' in __file__ else __file__)
    
    # Copy script to new location (installation)
    subprocess.run(['cp', scriptPath, '/tmp'])
    
    # Now run the installed script
    subprocess.run(['python3', f'/tmp/{scriptName}'])
    
    
def run():
    for _ in range(5):
        print('Running...')
        time.sleep(1)

if __name__=="__main__":
    if 'install' in sys.argv:
        installAndRun()
    else:
        run()

编辑:我刚刚意识到进程在这样调用时并没有结束。

似乎正确的组合是对 stdoutstderr 使用 Popen + subprocess.PIPE。代码现在看起来像这样:

import subprocess
import sys
import time
import os

def installAndRun(scriptPath, scriptName):
    print('Installing...')

    # Copy script to new location (installation)
    subprocess.run(['cp', scriptPath, '/tmp'])

    # Now run the installed script
    subprocess.Popen(['python3', f'/tmp/{scriptName}'], 
                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)

def run(scriptPath):
    for _ in range(5):
        print(f'Running... {scriptPath}')
        time.sleep(1)


if __name__=="__main__":

    scriptPath = os.path.realpath(__file__)
    scriptName = (__file__.split('/')[-1] if '/' in __file__ else __file__)

    if 'install' in sys.argv:
        installAndRun(scriptPath, scriptName)
    else:
        run(scriptPath)
  1. 不要使用“cp”复制脚本,而是使用shutil.copy()。

  2. 而不是“python3”,使用 sys.executable 以使用与原始脚本相同的解释器启动脚本。

  3. subprocess.Popen() 只要 child 进程不向 stdout 和 stderr 写入任何内容,并且不请求任何输出,则不使用任何其他方法。通常,除非未调用 communicate() 或 PIPE read/written 到,否则不会启动该过程。您必须使用 os.fork() 与 parent 分离(研究守护进程是如何制作的),然后使用:


p = subprocess.Popen([sys.executable, new_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
p.stdin.close() # If you do not need it
p.communicate()

或者不对 stdin、stderr 和 stdout 使用 subprocess.PIPE,并确保分叉时终端绑定到 child。在 os.fork() 之后,你可以用 parent 做你想做的,用 child 做你想做的。您可以将 child 绑定到您想要的任何终端或启动一个新的 shell 例如:

pid = os.fork()
if pid==0: # Code in this if block is the child
    <code to change the terminal and appropriately point sys.stdout, sys.stderr and sys.stdin>
    subprocess.Popen([os.getenv("SHELL"), "-c", sys.executable, new_path]).communicate()
  1. 请注意,如果需要,您可以使用 stdin、stderr 和 stdout 参数将 PIPE 指向 file-like objects。

  2. 要在 Windows 上分离,您可以使用 os.startfile() 或在线程中使用 subprocess.Popen(...).communicate() 。如果您然后 sys.exit() parent,child 应该保持打开状态。 (这就是它在 Windows XP 和 Python 2.x 上的工作方式,我没有尝试使用 Py3 或较新的 Win 版本)