【问题标题】:How to check if a string is a valid shell command using Python?如何使用 Python 检查字符串是否是有效的 shell 命令?
【发布时间】:2020-10-25 00:42:51
【问题描述】:

我正在制作一个程序,为 Windows 中的标准命令 shell 添加附加功能。例如,键入google 后跟关键字将打开一个新选项卡,其中谷歌搜索这些关键字等。只要输入不引用我创建的自定义函数,它就会被处理为使用@987654322 的shell 命令@。

由于我想预测我的输入何时不是有效命令并返回类似f"Invalid command: {rawCommand}" 的内容,我应该如何去做?

到目前为止,我已经尝试过subprocess.call(rawCommand),它还返回标准输出以及退出代码。所以看起来像这样:

>>> from subprocess import call
>>> a, b = call("echo hello!", shell=1), call("xyz arg1 arg2", shell=1)
hello!
'xyz' is not recognized as an internal or external command,
operable program or batch file.
>>> a
0
>>> b
1

我想简单地接收退出代码。关于如何做到这一点的任何想法?

【问题讨论】:

  • 您可以使用stdout=open(os.devnull, 'w') 忽略标准输出,但运行命令只是为了确定它是否“有效”并不是一个好主意,因为您可能还没有为副作用做好准备。
  • @chepner 哪些副作用最常见?尝试这样的事情时我应该注意什么?
  • @peki、stdout 和 stderr 可以被捕获。如果命令成功,用户可能希望查看标准输出。如果命令失败,则 stdout 和/或 stderr 可能会包含用户需要的信息,以使更改成功。

标签: python cmd subprocess


【解决方案1】:

一些 shell 有一个语法-检查模式(例如bash -n),但这是唯一一种与“尝试执行命令和走着瞧吧”。定义更大类的“立即”错误是fraught proposition:如果echo hello; ./foo 是无效的,因为foo 不能作为命令找到,那么false && ./foo 怎么办,它永远不会尝试运行它,或者@ 987654326@,可能会成功(或可能无法复制)? eval $(configure_shell); foo 可能会或可能不会操纵PATH 以找到foo 呢? foo || install_foo 呢,可能会出现故障?

因此,预期失败在任何有意义的意义上都是不可能的:您唯一真正的选择是捕获命令的输出/错误(如 cmets 中所述)和以一些有用的方式报告它们。

【讨论】:

  • 您的基本分析是正确的。但是,用户通过使用cmd 标记已指定正在使用Windows 系统。正如您所说,echo hello & .\foo 等命令的验证问题是一个令人担忧的命题。
  • @lit:我对cmd 不够好,无法为它编造有意义的例子(或为它确定一个语法检查选项,如果有的话),但正如你所说的那样完全影响原则。
【解决方案2】:

如果你有一天想要处理编码错误、取回你正在运行的命令的结果、超时或决定哪些退出代码不是 0 可能不会触发错误(我在看着你,java 运行时!),这是一个完成这项工作的完整函数:

import os
from logging import getLogger
import subprocess

logger = getLogger()


def command_runner(command, valid_exit_codes=None, timeout=300, shell=False, encoding='utf-8',
                   windows_no_window=False, **kwargs):
    """
    Whenever we can, we need to avoid shell=True in order to preseve better security
    Runs system command, returns exit code and stdout/stderr output, and logs output on error
    valid_exit_codes is a list of codes that don't trigger an error
    windows_no_window will hide the command window (works with Microsoft Windows only)
    
    Accepts subprocess.check_output arguments
        
    """

    # Set default values for kwargs
    errors = kwargs.pop('errors', 'backslashreplace')  # Don't let encoding issues make you mad
    universal_newlines = kwargs.pop('universal_newlines', False)
    creationflags = kwargs.pop('creationflags', 0)
    if windows_no_window:
        creationflags = creationflags | subprocess.CREATE_NO_WINDOW

    try:
        # universal_newlines=True makes netstat command fail under windows
        # timeout does not work under Python 2.7 with subprocess32 < 3.5
        # decoder may be unicode_escape for dos commands or utf-8 for powershell
        output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=shell,
                                         timeout=timeout, universal_newlines=universal_newlines, encoding=encoding,
                                         errors=errors, creationflags=creationflags, **kwargs)

    except subprocess.CalledProcessError as exc:
        exit_code = exc.returncode
        try:
            output = exc.output
        except Exception:
            output = "command_runner: Could not obtain output from command."
        if exit_code in valid_exit_codes if valid_exit_codes is not None else [0]:
            logger.debug('Command [%s] returned with exit code [%s]. Command output was:' % (command, exit_code))
            if isinstance(output, str):
                logger.debug(output)
            return exc.returncode, output
        else:
            logger.error('Command [%s] failed with exit code [%s]. Command output was:' %
                         (command, exc.returncode))
            logger.error(output)
            return exc.returncode, output
    # OSError if not a valid executable
    except (OSError, IOError) as exc:
        logger.error('Command [%s] failed because of OS [%s].' % (command, exc))
        return None, exc
    except subprocess.TimeoutExpired:
        logger.error('Timeout [%s seconds] expired for command [%s] execution.' % (timeout, command))
        return None, 'Timeout of %s seconds expired.' % timeout
    except Exception as exc:
        logger.error('Command [%s] failed for unknown reasons [%s].' % (command, exc))
        logger.debug('Error:', exc_info=True)
        return None, exc
    else:
        logger.debug('Command [%s] returned with exit code [0]. Command output was:' % command)
        if output:
            logger.debug(output)
        return 0, output

用法:

exit_code, output = command_runner('whoami', shell=True)

【讨论】:

  • 非常感谢!!使用getLogger确实很聪明,但是我对它真的很缺乏经验,所以这对我来说意味着一堆,呵呵...
  • 在此脚本中使用 getLogger 假设您已事先在某处配置了日志记录(请参阅 basicConfig 开头)。在任何情况下,这个子流程实现更可靠,因为它可以处理很多极端情况。如果您使用的是 Windows,如果您有特殊字符(重音等),请尝试使用参数 encoding='cp437'。祝你好运。
猜你喜欢
  • 1970-01-01
  • 2016-05-25
  • 2021-12-08
  • 2011-07-27
  • 2021-07-05
  • 2013-02-13
  • 2011-06-17
  • 1970-01-01
  • 2011-11-26
相关资源
最近更新 更多