简体   繁体   English

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

[英]Running multiple Bash commands interactively from Python

I have just come across pexpect and have been figuring out how to use it to automate various practices I would otherwise have to fill in manually in a command shell.我刚刚遇到pexpect并且一直在研究如何使用它来自动化各种实践,否则我将不得不在命令 shell 中手动填写。

Here's an example script:这是一个示例脚本:

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)

(I know these particular tasks do not necessarily require pexpect , just trying to understand its best practices.) (我知道这些特定任务不一定需要pexpect ,只是想了解它的最佳实践。)

Now, the above works.现在,上述工作。 It cd s to my local repo folder, lists the files there, stages my commits, and pushes to Github with authentication, all the while providing real-time output to the Python stdout.cd到我的本地 repo 文件夹,在那里列出文件,暂存我的提交,并通过身份验证推送到 Github,同时向 ZA7F5F35426B92741738 stdout1B563 提供实时 output。 But I have two areas I'd like to improve:但我有两个方面需要改进:

Firstly, .expect('#') between every line I would run in Bash (that doesn't require interactivity) is a little tedious.首先,我将在 Bash(不需要交互性)中运行的每一行之间.expect('#')有点乏味。 (And I'm not sure whether / why it always seems to work, whatever was the output in stdout - although so far it does.) Ideally I could just clump them into one multiline string and dispense with all those expect s. (而且我不确定它是否/为什么似乎总是有效,无论标准输出中的 output 是什么——尽管到目前为止它确实有效。)理想情况下,我可以将它们聚集成一个多行字符串并省去所有那些expect s。 Isn't there a more natural way to automate parts of the script that could be eg, a multiline string with Bash commands separated by ';'难道没有更自然的方法来自动化脚本的某些部分,例如,一个多行字符串,其中 Bash 命令用“;”分隔or '&&' or '||'?或'&&'或'||'?

Secondly, if you run a script like the above you'll see it times out after 60 seconds sharp, then yields a TimeoutError in Python.其次,如果你像上面那样运行一个脚本,你会看到它在 60 秒后超时,然后在 Python 中产生一个 TimeoutError。 Although - assuming the job fits within 60 seconds - it gets done, I would prefer something which (1) doesn't take unnecessarily long, (2) doesn't risk cutting off a >60 second process midway, (3) doesn't end the whole thing giving me an error in Python.虽然 - 假设工作在 60 秒内完成 - 它可以完成,但我更喜欢(1)不会花费不必要的时间,(2)不会冒中途中断 > 60 秒过程的风险,(3)不会t 结束整个事情给我 Python 的错误。 Can we instead have it come to a natural end, ie, when the shell processes are finished, that's when it stops running in Python too?我们是否可以让它自然结束,即当 shell 进程完成时,它也停止在 Python 中运行? (If (2) and (3) can be addressed, I could probably just set an enormous timeout value - not sure if there is better practice though.) (如果可以解决(2)和(3),我可能只设置一个巨大的timeout值 - 但不确定是否有更好的做法。)

What's the best way of rewriting the code above?重写上面代码的最佳方法是什么? I grouped these two issues in one question because my guess is there is a generally better way of using pexpect , which could solve both problems (and probably others I don't even know I have yet,).我将这两个问题归为一个问题,因为我的猜测是通常有一种更好的使用pexpect的方法,它可以解决这两个问题(可能还有其他我什至不知道的问题)。 and in general I'd invite being shown the best way of doing this kind of task.总的来说,我会邀请他们展示完成此类任务的最佳方式。

You don't need to wait for # between each command.您无需在每个命令之间等待# You can just send all the commands and ignore the shell prompts.您可以只发送所有命令而忽略 shell 提示。 The shell buffers all the inputs. shell 缓冲所有输入。

You only need to wait for the username and password prompts, and then the final # after the last command.您只需要等待用户名和密码提示,然后在最后一个命令之后的最后一个#

You also need to send an exit command at the end, otherwise you won't get EOF.最后还需要发送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)

If you're running into the 60 second timeout, you can use timeout=None to disable this.如果您遇到 60 秒超时,您可以使用timeout=None禁用此功能。 See pexpect timeout with large block of data from child看到来自孩子的大块数据的 pexpect 超时

You could also combine multiple commands in a single line:您还可以在一行中组合多个命令:

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)

Using && between the commands ensures that it stops if any of them fails.在命令之间使用&&可确保在其中任何一个失败时停止。

In general I wouldn't recommend using pexpect for this at all.一般来说,我根本不建议使用pexpect Make a shell script that does everything you want, and run the script with a single subprocess.Popen() call.制作一个 shell 脚本来执行您想要的所有操作,然后使用单个subprocess.Popen()调用运行该脚本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM