在 Python 子进程模块中过滤掉需要终端的命令 [英] Filter out command that needs a terminal in Python subprocess module

查看:38
本文介绍了在 Python 子进程模块中过滤掉需要终端的命令的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个机器人,它接受来自网络 (XMPP) 的命令并使用 Python 中的子进程模块来执行它们并将命令的输出发回.本质上,它是一个类似于 SSH 的基于 XMPP 的非交互式 shell.

I am developing a robot that accepts commands from network (XMPP) and uses subprocess module in Python to execute them and sends back the output of commands. Essentially it is an SSH-like XMPP-based non-interactive shell.

机器人只执行来自经过身份验证的可信来源的命令,因此允许任意 shell 命令(shell=True).

The robot only executes commands from authenticated trusted sources, so arbitrary shell commands are allowed (shell=True).

然而,当我不小心发送了一些需要 tty 的命令时,机器人卡住了.

However, when I accidentally send some command that needs a tty, the robot is stuck.

例如:

subprocess.check_output(['vim'], shell=False)
subprocess.check_output('vim', shell=True)

如果接收到上述每个命令,机器人就会卡住,机器人运行的终端就会损坏.

Should each of the above commands is received, the robot is stuck, and the terminal from which the robot is run, is broken.

虽然机器人只接收来自经过身份验证的可信来源的命令,但人为错误.我怎样才能让机器人过滤掉那些会破坏自己的命令?我知道有 os.isatty 但我该如何利用它?有没有办法检测那些坏"命令并拒绝执行它们?

Though the robot only receives commands from authenticated trusted sources, human errs. How could I make the robot filter out those commands that will break itself? I know there is os.isatty but how could I utilize it? Is there a way to detect those "bad" commands and refuse to execute them?

TL;DR:

说,有两种命令:

  • ls 这样的命令:不需要 tty 即可运行.
  • vim这样的命令:需要一个tty;如果没有给出 tty,则中断子进程.
  • Commands like ls: does not need a tty to run.
  • Commands like vim: needs a tty; breaks subprocess if no tty is given.

我怎么知道一个命令是 ls-like 还是 vim-like 并且拒绝运行命令,如果它是 vim-喜欢吗?

How could I tell a command is ls-like or is vim-like and refuses to run the command if it is vim-like?

推荐答案

你期望的是一个函数,它接收命令作为输入,并通过运行命令返回有意义的输出.

What you expect is a function that receives command as input, and returns meaningful output by running the command.

由于命令是任意的,对 tty 的要求只是可能发生的许多坏情况之一(其他包括运行无限循环),你的函数应该只关心它的运行周期,换句话说,一个命令是坏的"与否取决于它是否在有限的时间内结束,并且由于 subprocess 本质上是异步的,您可以直接运行命令并在更高的视野中处理它.

Since the command is arbitrary, requirement for tty is just one of many bad cases may happen (other includes running a infinite loop), your function should only concern about its running period, in other words, a command is "bad" or not should be determined by if it ends in a limited time or not, and since subprocess is asynchronous by nature, you can just run the command and handle it in a higher vision.

要播放的演示代码,您可以更改 cmd 值以查看其性能有何不同:

Demo code to play, you can change the cmd value to see how it performs differently:

#!/usr/bin/env python
# coding: utf-8

import time
import subprocess
from subprocess import PIPE


#cmd = ['ls']
#cmd = ['sleep', '3']
cmd = ['vim', '-u', '/dev/null']

print 'call cmd'
p = subprocess.Popen(cmd, shell=True,
                     stdin=PIPE, stderr=PIPE, stdout=PIPE)
print 'called', p

time_limit = 2
timer = 0
time_gap = 0.2

ended = False
while True:
    time.sleep(time_gap)

    returncode = p.poll()
    print 'process status', returncode

    timer += time_gap
    if timer >= time_limit:
        print 'timeout, kill process'
        p.kill()
        break

    if returncode is not None:
        ended = True
        break

if ended:
    print 'process ended by', returncode

    print 'read'
    out, err = p.communicate()
    print 'out', repr(out)
    print 'error', repr(err)
else:
    print 'process failed'

以上代码有三点值得注意:

Three points are notable in the above code:

  1. 我们使用 Popen 而不是 check_output 来运行命令,不像 check_output 会等待进程结束,Popen 立即返回,因此我们可以做进一步的事情来控制进程.

  1. We use Popen instead of check_output to run the command, unlike check_output which will wait for the process to end, Popen returns immediately, thus we can do further things to control the process.

我们实现了一个计时器来检查进程的状态,如果它运行时间过长,我们会手动杀死它,因为我们认为如果一个进程不能在有限的时间内结束,它就没有意义.这样你原来的问题就解决了,因为 vim 永远不会结束,它肯定会作为一个无意义"的命令被杀死.

We implement a timer to check for the process's status, if it runs for too long, we killed it manually because we think a process is not meaningful if it could not end in a limited time. In this way your original problem will be solved, as vim will never end and it will definitely being killed as an "unmeaningful" command.

定时器帮我们过滤掉不好的命令后,我们可以通过调用Popen对象的communicate方法得到命令的stdout和stderr,然后由您决定返回给用户什么.

After the timer helps us filter out bad commands, we can get stdout and stderr of the command by calling communicate method of the Popen object, after that its your choice to determine what to return to the user.

结论

不需要tty模拟,我们应该异步运行子进程,然后通过定时器控制它来决定是否应该杀死它,对于那些正常结束的,它安全且容易得到输出.

tty simulation is not needed, we should run the subprocess asynchronously, then control it by a timer to determine whether it should be killed or not, for those ended normally, its safe and easy to get the output.

这篇关于在 Python 子进程模块中过滤掉需要终端的命令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆