【问题标题】:Handling interactive shells with Python subprocess使用 Python 子进程处理交互式 shell
【发布时间】:2017-09-19 03:43:23
【问题描述】:

我正在尝试使用多处理池运行基于控制台的游戏的多个实例(地牢爬行石汤——自然是出于研究目的)来评估每次运行。

过去,当我使用池来评估类似代码(遗传算法)时,我使用subprocess.call 来拆分每个进程。然而,由于 dcss 的交互性很强,共享子 shell 似乎是有问题的。

我有我通常用于这种事情的代码,用 crawl 替换了我抛出 GA 的其他应用程序。还有比这更好的方法来处理高度交互的 shell 吗?我曾考虑为每个实例启动一个屏幕,但认为有一种更清洁的方法。我的理解是shell=True 应该生成一个子shell,但我想我是在以每次调用之间共享的方式生成一个子shell。

我应该提到我有一个运行游戏的机器人,所以我不希望发生来自用户端的任何实际交互。

# Kick off the GA execution
pool_args = zip(trial_ids,run_types,self.__population)
pool.map(self._GAExecute, pool_args)

---

# called by pool.map 
def _GAExecute(self,pool_args):
  trial_id       = pool_args[0]
  run_type       = pool_args[1]
  genome         = pool_args[2]
  self._RunSimulation(trial_id)

# Call the actual binary
def _RunSimulation(self, trial_id):
  command = "./%s" % self.__crawl_binary
  name    = "-name %s" % trial_id
  rc      = "-rc %s" % os.path.join(self.__output_dir,'qw-%s'%trial_id,"qw -%s.rc"%trial_id)
  seed    = "-seed %d" % self.__seed
  cdir    = "-dir %s" % os.path.join(self.__output_dir,'qw-%s'%trial_id)

  shell_command = "%s %s %s %s %s" % (command,name,rc,seed,cdir)
  call(shell_command, shell=True)

【问题讨论】:

    标签: python python-2.7 shell subprocess python-multiprocessing


    【解决方案1】:

    为每个调用指定具有唯一文件句柄的标准输入、标准输出和标准错误:

    import subprocess
    cmd  = ""
    fout = open('stdout.txt','w')
    fin  = open('stdin.txt','r')
    ferr = open('stderr.txt','w')
    subprocess.call(cmd, stdout=fout , stdin = fin , stderr=ferr )
    

    【讨论】:

    • 我真的可以用它来捕获 ttyrec 吗?
    • 好吧,我对 ttyrec 不熟悉。但是,您可以提供来自 fin 的输入,它可以是文件对象或任何类似文件的对象。
    • 如果您在 linux 中工作 - 您可以尝试使用 /dev/tty* 文件
    • 我需要关闭句柄还是由子进程处理?
    【解决方案2】:

    您确实可以将 stdin 和 stdout 关联到文件,如@napuzba 的回答:

    fout = open('stdout.txt','w')
    ferr = open('stderr.txt','w')
    subprocess.call(cmd, stdout=fout, stderr=ferr)
    

    另一种选择是使用Popen 而不是call。不同之处在于 call 等待完成(阻塞)而 Popen 不是,参见What's the difference between subprocess Popen and call (how can I use them)?

    使用 Popen,您可以将 stdout 和 stderr 保留在您的对象中,稍后再使用它们,而无需依赖文件:

    p = subprocess.Popen(cmd,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    p.wait()
    stderr = p.stderr.read()
    stdout = p.stdout.read()
    

    此方法的另一个潜在优势是您可以运行多个 Popen 实例而无需等待完成而不是拥有线程池:

    processes=[
      subprocess.Popen(cmd1,stdout=subprocess.PIPE, stderr=subprocess.PIPE),
      subprocess.Popen(cmd2,stdout=subprocess.PIPE, stderr=subprocess.PIPE),
      subprocess.Popen(cmd3,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    ]
    
    for p in processes:
      if p.poll():
         # process completed
      else:
         # no completion yet
    

    顺便说一句,如果可以的话,你应该avoidshell=True,如果你不使用它,Popen 期望将列表作为命令而不是字符串。不要手动生成此列表,而是使用shlex,它将为您处理所有极端情况,例如:

    Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    

    【讨论】:

    • 这很有帮助,但我很确定我需要阻塞线程池。它在 GA 中运行,因此必须在调用控制循环的下一次迭代之前完成对所有种群成员的评估。
    • 由于 for 循环中的 poll(),它被/可以阻塞。这个解决方案的好处是您实际上不需要自己管理线程/线程池来启动子进程的运行。
    猜你喜欢
    • 2023-03-09
    • 2022-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    相关资源
    最近更新 更多