为什么这个Linux命令在Python中执行时不起作用?

3 投票
2 回答
1257 浏览
提问于 2025-04-18 09:33

我想执行这个Linux命令:

“cat cflow_image.py | mailx -s "CFLOW Copy" foo@.foo.com”。我的需求是把这个命令放在Python脚本里执行。我正在使用subprocess模块来实现这个。

这是我写的代码:

def send_mail(mailid): 
   # This is mail the testbed info to the user
   mailid = args.mailID 
   print "* INFO file will be sent to your mailid *"
   subprocess.call("cat info.txt | mailx -s \"DEVSETUP\" {0}").format(mailid)

下面是执行时出现的错误:

Traceback (most recent call last):
  File "dev-installer.py", line 243, in <module>
    send_mail(args.mailID)
  File "dev-installer.py", line 204, in send_mail
    subprocess.call("cat info.txt | mailx -s \"DEVSETUP\" {0}").format(mailid)
  File "/sw/packages/python/2.7.4/lib/python2.7/subprocess.py", line 524, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/sw/packages/python/2.7.4/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/sw/packages/python/2.7.4/lib/python2.7/subprocess.py", line 1308, in _execute_child

2 个回答

1

你在传递一个 shell 命令,所以 Python 会调用一个 shell 来解析并执行你的命令。但是你告诉 Python 去执行一个叫做 cat info.txt | mailx -s "DEVSETUP" foo@.foo.com 的命令,也就是说,系统里应该有一个这个名字的可执行文件,但实际上并没有这样的命令。

subprocess.call 支持所有 Popen 构造函数的关键字参数。你可以传递一个关键字参数 shell=True,来告诉 Python 你传的是一些 shell 代码,而不是一个可执行文件的名字。

subprocess.call("cat info.txt | mailx -s \"DEVSETUP\" {0}".format(mailid),
                shell=True)

不过要注意,mailid 的值会在 shell 代码中被插入,如果它包含了 shell 特殊字符,比如 bob@example.com (Bob Smith)Bob Smith <bob@example.com>,那么就会出问题。你应该确保对 mailid 中的特殊字符进行引号处理。

你发的命令还有一个问题,就是如果在读取 info.txt 时发生了错误,系统是不会检测到的。你可以通过避免无用的使用 cat 来解决这个问题:

subprocess.call("<info.txt mailx -s \"DEVSETUP\" {0}".format(mailid),
                shell=True)

考虑到这个命令很简单,你根本不需要调用 shell。如果不涉及 shell,你就不需要担心引号的问题。你可以很容易地让 Python 打开 info.txt 文件进行读取。

body_file = open("info.txt")
status = subprocess.call(["mailx", "-s", "DEVSETUP", mailid], stdin=body_file)
body_file.close()
if status != 0:
    … handle mailx failure …
3

subprocess.call 是用来执行一个可执行文件的。这里你不是在传递一个可执行文件的路径,而是传递一个命令行指令

如果你想执行一个命令行指令,你需要明确告诉程序你想在一个命令行环境中执行这个指令,这可以通过在参数列表中加上 shell=True 来实现。需要注意的是,使用 shell=True 和用户提供的命令一起使用可能会带来安全隐患。

另外,如果你没有使用 shell=True(比如你没有特别说明),那么你应该传递一个字符串列表作为参数,这个列表代表已经解析好的参数列表。举个例子:

subprocess.call('ls -l')

这会尝试执行一个名为 ls -l 的命令,如果你的 PATH 中没有这个名字的可执行文件,可能会失败,而:

subprocess.call(['ls', '-l'])

则会调用 ls 这个可执行文件,并传递参数 -l

如果你不想手动写这样的列表,可以使用 shlex.split 来解析“命令行”:

subprocess.call(shlex.split('executable arg1 arg2 --option1 --option2 "string argument"'))

这样会得到以下调用:

subprocess.call(['executable', 'arg1', 'arg2', '--option1', '--option2', 'string argument'])

注意引号是如何被正确处理的(不过没有进行命令行扩展!)

撰写回答