使用字符串和列表的subprocess.call

51 投票
1 回答
52120 浏览
提问于 2025-04-17 17:18

我正在尝试用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 个回答

67

subprocess处理命令参数的规则其实有点复杂。

一般来说,运行外部命令时,应该使用 shell=False,并将参数作为一个序列传递。只有在需要使用 shell 内置命令或特定的 shell 语法时,才使用 shell=True;正确使用 shell=True 还要根据不同平台来调整,下面会详细说明。

根据文档

args 应该是程序参数的一个序列,或者是一个单独的字符串。默认情况下,如果 args 是一个序列,执行的程序是 args 中的第一个项目。如果 args 是一个字符串,它的解释是依赖于平台的,下面会详细描述。有关与默认行为的其他区别,请参见 shellexecutable 参数。除非另有说明,建议将 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 内置的(例如 dircopy)时,才需要指定 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,并带上参数 -ava/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 系统调用。

撰写回答