使用Python subprocess处理交互式shell

4
我正在尝试使用多进程池运行多个基于控制台的游戏(地牢爬行石汤——自然是为了研究目的),以评估每次运行。过去,当我使用池来评估类似的代码(遗传算法)时,我使用子进程调用(subprocess.call)来分离每个进程。然而,由于dcss非常交互式,共享子shell似乎存在问题。
我有我通常用于此类事情的代码,其中爬行取代了我用GA抛出的其他应用程序。是否有更好的处理高度交互式shell的方法?我曾考虑为每个实例启动一个screen,但认为有一种更清洁的方法。我的理解是,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)
2个回答

3

您确实可以将stdin和stdout与文件关联起来,就像@napuzba的答案中所述:

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

另一个选择是使用Popen而不是call。 区别在于call等待完成(是阻塞的),而Popen不是,参见subprocess Popen和call之间的区别(我如何使用它们)? 使用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

顺便提一下,如果可以的话,您应该避免使用shell=True,如果不使用它,则Popen期望将命令作为列表而不是字符串。不要手动生成此列表,而是使用shlex,它会为您处理所有边角情况,例如:

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

这很有帮助,但我相当确定我需要线程池是阻塞的。它在GA中运行,因此必须在调用控制循环的下一次迭代之前完成所有种群成员的评估。 - erik
1
由于poll()在for循环中使用,它是/可以是阻塞的。这种解决方案的好处在于,你实际上不需要自己管理线程/线程池来启动子进程运行。 - Guillaume

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吗? - erik
嗯,我不熟悉ttyrec。但是,您可以从“fin”提供输入,它可以是文件对象或任何类似文件的对象。 - napuzba
如果您在Linux上工作,可以尝试使用/dev/tty*文件。 - napuzba
我需要关闭句柄吗,还是subprocess会处理? - erik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接