使用字符串和列表的subprocess.call
我正在尝试用rsync配合subprocess.call来使用。有点奇怪的是,当我把一个字符串传给subprocess.call时,它能正常工作,但如果我传一个列表就不行。
用字符串调用sp.call:
In [23]: sp.call("rsync -av content/ writings_raw/", shell=True)
sending incremental file list
sent 6236 bytes received 22 bytes 12516.00 bytes/sec
total size is 324710 speedup is 51.89
Out[23]: 0
用列表调用sp.call:
In [24]: sp.call(["rsync", "-av", "content/", "writings_raw/"], shell=True)
rsync version 3.0.9 protocol version 30
Copyright (C) 1996-2011 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
64-bit files, 64-bit inums, 32-bit timestamps, 64-bit long ints,
socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
append, ACLs, xattrs, iconv, symtimes
rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you
are welcome to redistribute it under certain conditions. See the GNU
General Public Licence for details.
rsync is a file transfer program capable of efficient remote update
via a fast differencing algorithm.
Usage: rsync [OPTION]... SRC [SRC]... DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
or rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
or rsync [OPTION]... [USER@]HOST:SRC [DEST]
or rsync [OPTION]... [USER@]HOST::SRC [DEST]
or rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.
Options
-v, --verbose increase verbosity
-q, --quiet suppress non-error messages
--no-motd suppress daemon-mode MOTD (see manpage caveat)
... snipped....
repeated: --filter='- .rsync-filter'
--exclude=PATTERN exclude files matching PATTERN
--blocking-io use blocking I/O for the remote shell
-4, --ipv4 prefer IPv4
-6, --ipv6 prefer IPv6
--version print version number
(-h) --help show this help (-h is --help only if used alone)
...snipped ...
rsync error: syntax or usage error (code 1) at main.c(1438) [client=3.0.9]
Out[24]: 1
我用列表的方式有什么问题呢?你会怎么解决这个问题?我需要用列表,因为我想使用变量。当然,我也可以这样做:
sp.call("rsync -av "+Orig+" "+Dest, shell=True)
但我想了解一下subprocess
是怎么理解列表和字符串的区别的。
设置shell=False和列表:
In [36]: sp.call(['rsync', '-av', ORIG, DEST], shell=False)
sending incremental file list
sent 6253 bytes received 23 bytes 12552.00 bytes/sec
total size is 324710 speedup is 51.74
Out[36]: 0
设置shell=False和字符串:
In [38]: sp.call("rsync -av"+" "+ORIG+" "+DEST, shell=False)
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-38-0d366d3ef8ce> in <module>()
----> 1 sp.call("rsync -av"+" "+ORIG+" "+DEST, shell=False)
/usr/lib/python2.7/subprocess.pyc in call(*popenargs, **kwargs)
491 retcode = call(["ls", "-l"])
492 """
--> 493 return Popen(*popenargs, **kwargs).wait()
494
495
/usr/lib/python2.7/subprocess.pyc in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags)
677 p2cread, p2cwrite,
678 c2pread, c2pwrite,
--> 679 errread, errwrite)
680
681 if mswindows:
/usr/lib/python2.7/subprocess.pyc in _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite)
1257 if fd is not None:
1258 os.close(fd)
-> 1259 raise child_exception
1260
1261
OSError: [Errno 2] No such file or directory
1 个回答
subprocess
处理命令参数的规则其实有点复杂。
一般来说,运行外部命令时,应该使用 shell=False
,并将参数作为一个序列传递。只有在需要使用 shell 内置命令或特定的 shell 语法时,才使用 shell=True
;正确使用 shell=True
还要根据不同平台来调整,下面会详细说明。
根据文档:
args
应该是程序参数的一个序列,或者是一个单独的字符串。默认情况下,如果args
是一个序列,执行的程序是args
中的第一个项目。如果args
是一个字符串,它的解释是依赖于平台的,下面会详细描述。有关与默认行为的其他区别,请参见shell
和executable
参数。除非另有说明,建议将args
作为序列传递……如果shell
为 True,建议将args
作为字符串而不是序列传递。
当 shell=False
时:
在 Unix 系统上,如果
args
是一个字符串,这个字符串会被解释为要执行的程序的名称或路径。不过,这种情况只能在不传递参数给程序时使用。在 Windows 系统上,如果
args
是一个序列,它会按照将参数序列转换为字符串的方式进行转换。这是因为底层的CreateProcess()
是基于字符串操作的。
当 shell=True
时:
在 Unix 系统上,使用
shell=True
时,默认的 shell 是/bin/sh
。如果args
是一个字符串,这个字符串指定了通过 shell 执行的命令。这意味着这个字符串必须完全按照在 shell 提示符下输入的格式来写。例如,包含空格的文件名需要用引号或反斜杠转义。如果args
是一个序列,第一个项目指定命令字符串,后面的项目将被视为传递给 shell 的额外参数。在 Windows 系统上,使用
shell=True
时,COMSPEC
环境变量指定了默认的 shell。只有当你想执行的命令是 shell 内置的(例如dir
或copy
)时,才需要指定shell=True
。运行批处理文件或控制台可执行文件时不需要shell=True
。
(所有强调部分为我所加)
为了完整起见,下面是你在 UNIX 系统上四个示例的结果:
字符串与 shell=True
subprocess.call("rsync -av a/ b/", shell=True)
会调用 sh -c "rsync -av a/ b/"
,这会执行 shell 脚本 rsync -av a/ b/
;shell 会将其解析为调用 rsync
,并带上参数 -av
、a/
、b/
,所以这样是可以正常工作的。
注意,如果任何参数中包含空格或特殊的 shell 字符,就需要手动转义,这使得这种方法比较脆弱。
列表与 shell=True
subprocess.call(["rsync", "-av", "a/", "b/"], shell=True)
会调用 sh -c "rsync" -av a/ b/
,这会执行 shell 脚本 rsync
,并将 $0 设置为 -av
,$1 设置为 a/
,$2 设置为 b/
。这个 shell 脚本只是调用 rsync
,没有参数(忽略 $0、$1、$2),所以你会看到一大堆帮助文本。
让这个工作的一种方法是 subprocess.call(['rsync "$@"', "rsync", "-av", "a/", "b/"], shell=True)
。这会调用一个 shell 脚本,将参数传递给 rsync
。注意这个额外的 rsync
参数是必要的,用来设置 $0
(注意 $@
的扩展是从 $1 开始的)。这并不是一个理想的解决方案,因此在使用 shell=True
时,使用序列的情况非常少见。
字符串与 shell=False
subprocess.call("rsync -av a/ b/")
会尝试在你的 $PATH 中找到一个名为 rsync -av a/ b/
的二进制文件。由于没有这样的二进制文件,所以你会收到来自 subprocess
的错误。当使用字符串和 shell=False
时,无法向程序提供任何参数。
列表与 shell=False
subprocess.call(["rsync", "-av", "a/", "b/"])
会在你的 $PATH 中调用 rsync
二进制文件,将 rsync
作为 argv[0],-av
作为 argv[1],a/
作为 argv[2],b/
作为 argv[3]。不需要转义参数,因为它们会直接传递给 execve
系统调用。