子进程中的bash通配符匹配
如果我有一个文件夹,想要删除里面除了一个文件以外的所有文件,我可能会在bash中这样做:
cd /tmp/a
rm -rf !(specialfile)
cd -
把这个操作用最简单的Python代码来写却失败了:
>>> subprocess.Popen( 'cd /tmp/a; rm -rf !(specialfile); cd -', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
并且出现了这个提示:
('', "/bin/sh: -c: line 0: syntax error near unexpected token `('\n/bin/sh: -c: line 0: `cd /tmp/a; rm -rf !(specialfile); cd -'\n")
在Python中,接下来最好的办法似乎是:
p = '/tmp/a'
for i in os.listdir( p ):
if i != 'specialfile':
os.remove( os.path.join( p, i ) )
但当然,这样做对文件和子文件夹的处理效果并不好。有没有更好的方法呢?
2 个回答
2
更新:正如 @isedev 和 OP @JohnSchmitt 在评论中指出的,subprocess.Popen
调用的是 sh
,而不是 bash
(而且 sh
可能是也可能不是 bash
,这取决于你使用的平台),但是使用扩展模式匹配操作符 !(...)
需要 (a) 使用 bash
并且 (b) 开启 extglob
选项(下面会有更多背景信息)。
因此,解决方法是:
- 明确调用
bash
,并通过-c
命令行选项传递命令字符串。 - 通过
-O
命令行选项开启extglob
shell 选项(如果不这样做,模式!(specialfile)
会导致 OP 遇到的语法错误)。
借用 @JohnSchmitt 自己的评论,我们可以得到:
subprocess.Popen("bash -O extglob -c 'cd /tmp/a; rm -rf !(file2); cd -'",
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
(不太优雅的替代方案是在 rm
命令之前,在 bash 命令字符串中添加 shopt -s extglob;
。)
背景:
!(specialfile)
是一个 扩展 模式匹配操作符的例子(可以查看 man bash
的 Pattern Matching
部分);这些扩展操作符默认是没有启用的;使用 shopt -s extglob
可以启用它们(使用 shopt -u extglob
可以禁用它们)。
1
你可以使用os.walk,正如@Bakuriu提到的那样。很重要的一点是,要从底部向上遍历文件夹树,这样才能确保在删除文件夹时,里面是空的,除了那个包含“specialfile”的文件夹。这就是为什么在os.rmdir
命令中需要使用try
语句的原因。
import os
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
if name != 'specialfile':
os.remove(os.path.join(root, name))
for name in dirs:
try:
os.rmdir(os.path.join(root, name))
except:
pass