Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/314.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
筛选出Python子流程模块中需要终端的命令_Python_Terminal_Subprocess_Tty - Fatal编程技术网

筛选出Python子流程模块中需要终端的命令

筛选出Python子流程模块中需要终端的命令,python,terminal,subprocess,tty,Python,Terminal,Subprocess,Tty,我正在开发一个机器人,它接受来自网络(XMPP)的命令,并使用Python中的子流程模块来执行这些命令,并发送回命令的输出。本质上,它是一个类似于SSH的基于XMPP的非交互式shell 机器人只执行来自经过验证的可信源的命令,因此允许使用任意shell命令(shell=True) 然而,当我不小心发送了一些需要tty的命令时,机器人被卡住了 例如: subprocess.check_output(['vim'], shell=False) subprocess.check_output('vi

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

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

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

例如:

subprocess.check_output(['vim'], shell=False)
subprocess.check_output('vim', shell=True)
如果收到上述每一个命令,则机器人会被卡住,运行机器人的终端也会断开

虽然机器人只接收来自经过验证的可信来源的命令,但人类会犯错误。我怎样才能让机器人过滤掉那些会自动中断的命令呢?我知道有
os.isatty
,但我如何利用它呢?有没有办法检测那些“坏”命令并拒绝执行它们

TL;博士

例如,有两种命令:

  • ls
    :这样的命令不需要tty来运行
  • vim
    这样的命令:需要tty;如果未给出tty,则中断子进程

我怎么知道一个命令是
ls
-like还是
vim
-like,如果它是
vim
-like,我会拒绝运行该命令?

好吧,SSH已经是一个允许用户运行的工具。身份验证非常棘手,请注意,从安全角度来看,构建您描述的软件有点风险

没有办法确定进程是否需要tty。而且没有
os.isatty
方法,因为如果您运行的子进程需要一个,并不意味着就有一个。:)


一般来说,如果你考虑一个白色的命令列表,那么从安全的角度来看,解决这个问题可能更安全。你可以选择那个白名单来避免需要tty的事情,因为我认为你不会轻易摆脱这个问题

非常感谢@J.F.Sebastia的帮助(参见问题下方的评论),我已经为我的案例找到了解决方案(变通?)

vim
断开端子而
ls
断开端子的原因是
vim
需要tty。正如Sebastia所说,我们可以使用
pty.openpty()
为vim提供一个pty。输入pty保证命令不会破坏终端,我们可以添加一个
timout
来自动终止这些进程。以下是(脏的)工作示例:

#!/usr/bin/env python3 import pty from subprocess import STDOUT, check_output, TimeoutExpired master_fd, slave_fd = pty.openpty() try: output1 = check_output(['ls', '/'], stdin=slave_fd, stderr=STDOUT, universal_newlines=True, timeout=3) print(output1) except TimeoutExpired: print('Timed out') try: output2 = check_output(['vim'], stdin=slave_fd, stderr=STDOUT, universal_newlines=True, timeout=3) print(output2) except TimeoutExpired: print('Timed out') #!/usr/bin/env蟒蛇3 进口公司 从子流程导入标准输出中,检查\u输出,TimeoutExpired 主线程,从线程=pty.openpty() 尝试: output1=检查输出(['ls','/',stdin=slave\u fd,stderr=STDOUT,universal\u newlines=True,timeout=3) 打印(输出1) 除TimeoutExpired外: 打印('超时') 尝试: output2=检查输出(['vim'],stdin=slave\u fd,stderr=STDOUT,universal\u newlines=True,timeout=3) 打印(输出2) 除TimeoutExpired外: 打印('超时')
请注意,我们需要处理的是stdin,而不是stdout或stderr。

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

由于该命令是任意的,对tty的要求只是可能发生的许多不好的情况之一(其他包括运行无限循环),您的函数应该只关注它的运行周期,换句话说,一个命令是否“坏”应该由它是否在有限的时间内结束来决定,由于
子流程
本质上是异步的,因此您可以运行该命令并在更高的视野中处理它

要播放演示代码,您可以更改
cmd
值以查看其执行方式的不同:

#!/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'
上述代码中有三点值得注意:

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

  • 我们实现了一个计时器来检查进程的状态,如果它运行的时间太长,我们会手动终止它,因为我们认为如果一个进程不能在有限的时间内结束,它就没有意义。通过这种方式,您原来的问题将得到解决,因为
    vim
    永远不会结束,它肯定会作为一个“毫无意义”的命令被杀死

  • 在计时器帮助我们过滤出错误的命令之后,我们可以通过调用
    Popen
    对象的
    communicate
    方法来获取命令的stdout和stderr,然后您可以选择返回给用户的内容

  • 结论


    不需要tty模拟,我们应该异步运行子进程,然后用定时器控制它,以确定是否应该终止它,对于那些正常结束的子进程,它安全且易于获得输出。

    您可以参考我在中的答案:,它使用伪终端使stdout无阻塞,并在句柄stdin/stdout中使用select

    我可以将
    命令
    var修改为
    'vim'
    。剧本也很好

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import os
    import sys
    import select
    import termios
    import tty
    import pty
    from subprocess import Popen
    
    command = 'vim'
    
    # save original tty setting then set it to raw mode
    old_tty = termios.tcgetattr(sys.stdin)
    tty.setraw(sys.stdin.fileno())
    
    # open pseudo-terminal to interact with subprocess
    master_fd, slave_fd = pty.openpty()
    
    # use os.setsid() process the leader of a new session, or bash job control will not be enabled
    p = Popen(command,
              preexec_fn=os.setsid,
              stdin=slave_fd,
              stdout=slave_fd,
              stderr=slave_fd,
              universal_newlines=True)
    
    while p.poll() is None:
        r, w, e = select.select([sys.stdin, master_fd], [], [])
        if sys.stdin in r:
            d = os.read(sys.stdin.fileno(), 10240)
            os.write(master_fd, d)
        elif master_fd in r:
            o = os.read(master_fd, 10240)
            if o:
                os.write(sys.stdout.fileno(), o)
    
    # restore tty settings back
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty)
    

    为什么要使用
    检查输出()
    ?您需要捕获任意命令的标准输出吗?我会尝试你也可以@J.F.Sebastian提供一个伪tty命令似乎是个好主意,但我没有成功尝试这个。我不熟悉fds,像
    ls
    vim
    这样的命令在fds方面有什么不同吗?我可以从他们的fds中判断命令是否“坏”(需要tty)吗?关键是,对于好的、坏的和丑陋的命令,您使用相同的
    pty
    启用的代码。我提供了几个工作代码示例的链接,这些示例演示了如何从子流程读取输出,如果子流程的标准流连接起来,则可能会改变其行为