运行 多个 Bash 命令以交互方式来自 Python

Running multiple Bash commands interactively from Python

我刚刚遇到 pexpect 并且一直在弄清楚如何使用它来自动执行各种操作,否则我将不得不在命令中手动填写 shell。

这是一个示例脚本:

import pexpect, sys

child = pexpect.spawn("bash", timeout=60)
child.logfile = sys.stdout
child.sendline("cd /workspace/my_notebooks/code_files")
child.expect('#')
child.sendline('ls')
child.expect('#')
child.sendline('git add .')
child.expect('#')
child.sendline('git commit')
child.expect('#')
child.sendline('git push origin main')
child.expect('Username .*:')
child.sendline(<my_github_username>)
child.expect('Password .*:')
child.sendline(<my_github_password>)
child.expect('#')
child.expect(pexpect.EOF)

(我知道这些特定任务不一定需要 pexpect,只是想了解它的最佳实践。)

现在,上述工作。它 cd 到我的本地 repo 文件夹,列出那里的文件,暂存我的提交,并通过身份验证推送到 Github,同时向 Python stdout 提供实时输出。但我有两个方面需要改进:

首先,.expect('#') 在 Bash 中 运行 的每一行之间(不需要交互)有点乏味。 (而且我不确定它是否/为什么它似乎总是有效,无论 stdout 中的输出是什么 - 尽管到目前为止它确实有效。)理想情况下,我可以将它们聚集成一个多行字符串并省去所有这些 expect秒。难道没有更自然的方法来自动化脚本的某些部分,例如,一个多行字符串,其中 Bash 命令由 ';' 分隔或“&&”或“||”?

其次,如果您 运行 像上面这样的脚本,您会看到它在 60 秒后突然超时,然后在 Python 中产生 TimeoutError。虽然 - 假设工作在 60 秒内完成 - 它完成了,但我更喜欢(1)不会花费不必要的时间,(2)不会冒中途中断> 60秒的过程的风险,(3)不会'不要结束整个事情给我 Python 中的错误。我们能否让它自然结束,即,当 shell 进程完成时,也就是它在 Python 中停止 运行ning 的时候? (如果 (2) 和 (3) 可以解决,我可能只需设置一个巨大的 timeout 值——但不确定是否有更好的做法。)

重写上面代码的最佳方法是什么?我将这两个问题归为一个问题,因为我的猜测是有一种通常更好的使用 pexpect 的方法,它可以解决这两个问题(可能还有其他我什至不知道的问题!),并且通常我希望大家向我展示完成此类任务的最佳方式。

您无需在每个命令之间等待 #。您可以只发送所有命令并忽略 shell 提示。 shell 缓冲所有输入。

您只需要等待用户名和密码提示,然后是最后一个命令后的最后#

你还需要在最后发送一个exit命令,否则你不会得到EOF。

import pexpect, sys

child = pexpect.spawn("bash", timeout=60)
child.logfile = sys.stdout
child.sendline("cd /workspace/my_notebooks/code_files")
child.sendline('ls')
child.sendline('git add .')
child.sendline('git commit')
child.sendline('git push origin main')
child.expect('Username .*:')
child.sendline(<my_github_username>)
child.expect('Password .*:')
child.sendline(<my_github_password>)
child.expect('#')
child.sendline('exit')
child.expect(pexpect.EOF)

如果您运行进入 60 秒超时,您可以使用 timeout=None 来禁用它。参见 pexpect timeout with large block of data from child

您也可以在一行中组合多个命令:

import pexpect, sys

child = pexpect.spawn("bash", timeout=60)
child.logfile = sys.stdout
child.sendline("cd /workspace/my_notebooks/code_files && ls && git add . && git commit && git push origin main')
child.expect('Username .*:')
child.sendline(<my_github_username>)
child.expect('Password .*:')
child.sendline(<my_github_password>)
child.expect('#')
child.sendline('exit')
child.expect(pexpect.EOF)

在命令之间使用 && 可确保在任何命令失败时停止。

一般来说,我根本不建议为此使用 pexpect。制作一个 shell 脚本来完成你想要的一切,然后 运行 脚本只需一次 subprocess.Popen() 调用。