Tar 没有排除目录?

Tar 没有排除目录?

我刚刚尝试在创建 tar 存档时排除几个目录。目录结构相当简单(Centos 6,tar v1.23):

/test/t1
     /t2
     /t3
     ...

每个子目录(t1、t2、t3、...)包含一些 txt 文件。没什么不寻常的。

好的,我们来试试这个:

tar czvf test.tar.gz test/ --exclude={"t2"}

失败,t2子目录包含在档案中。

tar czvf test.tar.gz test/ --exclude={"t2",""}

成功,t2被排除——正如预期的那样。

我尝试在笔记本电脑(Ubuntu18.04,tar v1.29)上使用相同的目录结构重现相同的情况。这里,两个命令都失败了 - 目录t2包含在创建的存档中!

  1. 为什么提供的单个目录条目{}不起作用?
  2. 为什么在不同的环境下结果会不同?

这是怎么回事?这与 tar 版本有关吗?依赖于 Linux 发行版?查看 tar 手册(当前版本,v1.32)和 tar 更新日志都没有给我任何答案。

答案1

我们从结尾开始吧。

为什么在不同的环境下结果会不同?

在某些版本中,tar --exclude=…仅对位于test/其后的路径(例如)进行计数。这意味着它tar … test/ --exclude=t2就像根本没有一样工作--exclude。我的 Debian 9 中的 GNU tar1.29 肯定是这样的。

您声称在 1.23 中--exclude,路径有效(至少在某些情况下)。我没有理由不相信您。事实上,我tar在 Debian 7 中达到了 GNU 1.26,它的行为就像您描述的 1.23 一样。

结论:放置--exclude=…在类似路径之前test/


为什么提供的单个目录条目{}不起作用?

这篇文章不是关于 的tar,而是关于 shell 和 shell 执行(或不执行)的括号扩展。并非所有 shell 都这样做。Bash 确实,Zsh 也是如此。普通 POSIX shell 没有这样的功能。

在支持括号扩展的 shell 中调用:

printf '<%s> ' --exclude={"t2"}; echo
printf '<%s> ' --exclude={"t2",""}; echo
printf '<%s> ' --exclude={foo,bar,"baz qux"}; echo

在每个输出中,无论里面是什么,<>都会得到一个单独的参数printf。您将分别看到:

<--exclude={t2}>
<--exclude=t2> <--exclude=>
<--exclude=foo> <--exclude=bar> <--exclude=baz qux>

我以前<>能够肯定地说可能存在多个参数,而不是带有空格的单个参数。

您可能认为--exclude={foo,bar,baz}make {foo,bar,baz}get totar并且该工具会将其解释为某种列表。不。shell 扩展了此语法并生成多种的启动之前的单词tar。该工具获取这些单词作为命令行参数,将它们解释为选项,并且不知道其中涉及一些括号;就像printf获取其参数一样。

现在出现了一个怪癖:要进行括号扩展,括号内必须至少有一个逗号 ( ,) 或点点 ( ,它有不同的用途)。来自 Bash 的链接手册:..

正确格式的括号扩展必须包含未加引号的左括号和右括号,以及至少一个未加引号的逗号或有效的序列表达式。任何格式错误的括号扩展均保持不变。

我们可能会调用--exclude={"t2"}一个格式错误的括号扩展。字符串保持不变,tar几乎与输入时一样(几乎,因为引号被删除)。该工具将排除名为 的文件{t2}。如您所见,没有理由排除t2t2不是{t2}

另一方面,--exclude={"t2",""}是正确形成的括号扩展,它扩展为--exclude=t2 --exclude=。后者不排除任何内容,但tar不会抱怨。前者t2按照您的意愿排除。

--exclude={foo,bar,baz,whatever}借助 shell 的功能,可以节省您一次又一次的输入--exclude=。但最终tar会得到

--exclude=foo --exclude=bar --exclude=baz --exclude=whatever

就像您输入了四条--exclude=语句一样。

如果只是要foo排除,那么您就不能将“列表”缩短为--exclude={foo},而应该只是--exclude=foo。您发现您可以使用--exclude={foo,},但这非常麻烦且毫无用处。请记住,这些括号不是必需的tar,它们永远不会到达那里。

请注意,如果您想排除任何文件(任何类型(包括目录类型的文件)字面上地那么{foo,bar,baz,whatever}您需要引用以防止大括号扩展开始:

--exclude='{foo,bar,baz,whatever}'

相关内容