如何在不包括目录本身的情况下打包文件和文件夹?

523

我通常会这样做:

tar -czvf my_directory.tar.gz my_directory

如果我只想包含my_directory中的所有内容(包括任何隐藏的系统文件),但不包括该目录本身怎么办?我不想要:

my_directory
   --- my_file
   --- my_file
   --- my_file

我想要:

my_file
my_file
my_file

这是使用 tar -czf 的默认行为吗?在我的情况下,它只存储文件而不是目录。当我仅对目录进行 tar 操作时,它会包含目录,但是使用 tar -czf 时,它只添加文件。 - Durga Swaroop
22个回答

822

使用 tar 的 -C 开关:

tar -czvf my_directory.tar.gz -C my_directory .

-C my_directory 表示告诉 tar 把当前目录切换到 my_directory,然后 . 意味着“添加整个当前目录”(包括隐藏文件和子目录)。

确保在执行 . 命令之前先使用 -C my_directory,否则您将会得到当前目录中的文件。

警告:您将会得到形如 ./file-name.ext 的条目而非 file-name.ext

如果您需要以 file-name.ext 形式的条目,请参考其他答案。


6
+1 谢谢!我漏掉了该死的“.”,真是太恼人了。 - JCotton
17
与大多数选项不同,-C 在要处理的文件列表中出现的位置上进行处理。考虑以下命令:tar --create --file=foo.tar -C /etc passwd hosts -C /lib libc.a我总是尝试 tar -czvf my_directory.tar.gz * -C my_directory,但它不起作用。 -C 的位置很重要!该死的 tar... - m-ric
79
不完美 - tar文件包含'.'和./file1,而不仅仅是file1。我喜欢下面mateusza提出的解决方案,在解压时使用--strip-components选项。 - Ivan
46
它会在 .tar.gz 中创建一个 . 作为根目录。 - Anonymous
4
对于不想要 . 父目录的人,这里是解决方案 - aross
显示剩余5条评论

269
cd my_directory/ && tar -zcvf ../my_dir.tgz . && cd - 

以下一行代码可以完成任务。它同样适用于隐藏文件。在bash中,"*"至少在路径名扩展中不会展开隐藏文件。以下是我的实验:

command */.*

$ mkdir my_directory
$ touch my_directory/file1
$ touch my_directory/file2
$ touch my_directory/.hiddenfile1
$ touch my_directory/.hiddenfile2
$ cd my_directory/ && tar -zcvf ../my_dir.tgz . && cd ..
./
./file1
./file2
./.hiddenfile1
./.hiddenfile2
$ tar ztf my_dir.tgz
./
./file1
./file2
./.hiddenfile1
./.hiddenfile2

44
不完美——tar文件包含.和./file1而不仅仅是file1。我喜欢下面mateusza提出的使用--strip-components来解压缩的解决方案。 - Ivan
30
@Ivan,如果你将命令中的 . 替换为 *,那么命令就变成了 cd my_directory/ && tar -zcvf ../my_dir.tgz * && cd ..,这样它就能像你期望的那样工作了。 - Anonymous
5
您也可以使用子shell,这样您当前的shell工作目录就不会改变:$ (cd my_directory/ && tar -zcvf ../my_dir.tgz .) - alecbz
8
有人知道为什么这个如此复杂吗?看起来像是tar创建者一个很大的疏忽... - information_interchange
5
在当前的Ubuntu系统中,这会给我一个包含名为“.”的文件夹的归档文件。 - user585776
显示剩余3条评论

80

您也可以像平常一样创建存档文件,并使用以下命令进行提取:

tar --strip-components 1 -xvf my_directory.tar.gz

5
这种解决方案在处理之前创建的tarball时特别有效,因为可能不知道所有要求时所需的。 - Digger
10
注意:--strip-components 是 GNU 的扩展功能。 - zneak
5
这个答案可以通过在上下文中提供“通常情况下”的例子来改进。 - vhs
3
如果tar文件的创建在我的一方,但提取不在我的控制范围内,并且不希望将作为根目录,则这并不能解决问题。 - WesternGun

61

简述(不包括././file1!)

find /my/dir/ -printf "%P\n" | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

在一些条件下(仅存档文件、目录和符号链接):

find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

说明

很遗憾,以下归档文件中包含父目录./

tar -czf mydir.tgz -C /my/dir .

您可以使用--transform配置选项将该目录中的所有文件移出,但这并不能摆脱.目录本身。随着命令越来越难以控制。
您可以使用$(find ...)向命令添加文件列表(如magnus' answer中所示),但这可能会导致"文件列表过长"错误。最好的方法是将其与tar的-T选项相结合,像这样:
find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

基本上它列出您目录下的所有文件(-type f),链接(-type l)和子目录(-type d),使用-printf“%P \ n”使所有文件名相对,并将其传递给tar命令(它使用-T -从STDIN获取文件名)。需要-C选项,以便tar知道相对名称的文件位于哪里。 --no-recursion标志是为了防止tar递归进入它被告知要存档的文件夹(导致重复文件)。
如果您需要对文件名执行特殊操作(过滤,跟随符号链接等),则find命令非常强大,您可以通过仅删除上述命令中的tar部分来测试它:
$ find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d
> textfile.txt
> documentation.pdf
> subfolder2
> subfolder
> subfolder/.gitignore

例如,如果您想过滤PDF文件,请添加! -name '*.pdf'
$ find /my/dir/ -printf "%P\n" -type f ! -name '*.pdf' -o -type l -o -type d
> textfile.txt
> subfolder2
> subfolder
> subfolder/.gitignore

非GNU的find命令

该命令使用printf(在GNU find中可用)告诉find以相对路径打印其结果。但是,如果您没有GNU find,则可以使用以下命令将路径转换为相对路径(使用sed删除父级):

find /my/dir/ -type f -o -type l -o -type d | sed s,^/my/dir/,, | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

4
好的回答。非常详尽,最重要的是,完美地解决了问题。 - Alex
1
好的解决方法。为什么tar这么蠢? - SandRock
我会在.bashrc中定义一个名为tar_content的函数来执行此操作。 - WesternGun
1
为什么这不是最受欢迎和被接受的解决方案呢?我非常感激:-T - 对我很有帮助。 - matt
1
没有影响,但感谢提示。我从现在开始会使用它 :) - matt
显示剩余2条评论

39
请看 --transform/--xform,它给了你在将文件添加到归档时对文件名进行修改的机会。
% mkdir my_directory
% touch my_directory/file1
% touch my_directory/file2
% touch my_directory/.hiddenfile1
% touch my_directory/.hiddenfile2
% tar -v -c -f my_dir.tgz --xform='s,my_directory/,,' $(find my_directory -type f)
my_directory/file2
my_directory/.hiddenfile1
my_directory/.hiddenfile2
my_directory/file1
% tar -t -f my_dir.tgz 
file2
.hiddenfile1
.hiddenfile2
file1

Transform表达式与sed相似,我们可以使用除/以外的分隔符(如上例中的,)。
https://www.gnu.org/software/tar/manual/html_section/tar_52.html


4
我会这样做。其他的都只是把戏! - jwg
1
好的解决方案,但可能会导致“文件列表过长”。我的解决方案可以避免这种情况,并且更加灵活。 - aross
1
这是一个很棒的解决方案。您还可以多次传递“--xform”以处理多个路径。 - elig

30
cd my_directory
tar zcvf ../my_directory.tar.gz *

2
Hal明确询问了隐藏文件。你还需要.??*。 - PanCrit
2
-1:这不会将隐藏文件添加到tar中。请参考tbman的答案。 - dubek
除了隐藏文件之外,这个解决方案是最好的! - The Human Cat

20

这个答案在大多数情况下都可以使用。但请注意,文件名以如下方式存储在tar文件中:./file1而不仅仅是file1。我发现,当使用此方法操作用作BuildRoot包文件的tarballs时,这会导致问题。

一种解决方法是使用一些Bash通配符列出除..之外的所有文件,例如:

tar -C my_dir -zcvf my_dir.tar.gz .[^.]* ..?* *

这是我从这个答案学到的技巧。

现在,如果没有与..?*.[^.]*匹配的文件,tar将返回一个错误,但它仍然能够正常工作。如果错误是一个问题(您正在脚本中检查成功),可以使用以下方法:

shopt -s nullglob
tar -C my_dir -zcvf my_dir.tar.gz .[^.]* ..?* *
shopt -u nullglob

尽管现在我们正在处理 shell 选项,但我们可能会决定让 * 匹配隐藏文件更加整洁:
shopt -s dotglob
tar -C my_dir -zcvf my_dir.tar.gz *
shopt -u dotglob

这种方法可能无法在当前目录中使用shell通配符*,因此可以选择使用以下方式:

shopt -s dotglob
cd my_dir
tar -zcvf ../my_dir.tar.gz *
cd ..
shopt -u dotglob

2
当我执行这个命令时,我会收到奇怪的错误提示:“tar: start.sh: Cannot stat: No such file or directory”,而且所有当前目录下的文件都会出现这种情况!我该如何避免这种情况? - BrainStone
@BrainStone 我得到了完全相同的结果。 - mbmast
这种方法不起作用 - 至少在某些 shell(例如 bash,版本 5.0.17,Ubuntu 20.04)中不起作用 - 因为 * 通配符在 tar 接管并更改目录(-C my_dir)之前由 shell 进行评估。因此,它尝试将文件归档到 tar 命令执行的当前目录中,而不是更改后的目录 my_dir。如果当前目录中的文件名恰好与更改后的目录 my_dir 中的名称匹配,则可能会有所帮助,但这通常不可靠。 :) ... 最有可能的是,这就是上述错误的原因。 - Trevor
1
@Trevor 我认为第四个示例是围绕这个问题工作的(首先进入目录,然后运行不带 -C 选项的 tar 命令)。 - Rob Fisher

6
cd my_directory && tar -czvf ../my_directory.tar.gz $(ls -A) && cd ..

对我来说,这个方法很有效,它包含了所有隐藏文件,而不需要像 tomoe的答案 中那样将所有文件放在名为“.”的根目录下:


2
如果tar命令的输入参数是由带有空格的文件或目录名的ls命令输出结果构成的,那么tar命令将会出错。这里有更多不使用ls命令的原因。 - Jimmix

4
cd DIRECTORY
tar -czf NAME.tar.gz  *

星号将包含所有内容,即使是隐藏的。

4

命令

创建一个标准的压缩文件。

find my_directory/ -maxdepth 1 -printf "%P\n" | tar -cvf my_archive.tar -C my_directory/ -T -

打包的文件和目录在归档文件的根目录中,没有路径信息,更深层次的文件有相对路径。
没有奇怪的 './' 前缀在文件和目录名前。 ('./file')
不包括特殊文件 '.'。

看起来需要另外一个工具,比如 find 或者 lsls -A -1),tar 仅使用其参数无法选择文件并创建满足这些要求的压缩文件。

使用上述命令可以创建一个归档 tar 文件,可以进一步处理或交付给他人而不会出现奇怪的问题或需要解释或工具来解包。

参数说明

-maxdepth 1
最多向下遍历 1 级,不递归。
-printf
在标准输出上打印格式
%P 文件的名称,去除找到它的起始点的名称。
\n 换行符
printf 不会在字符串末尾添加换行符,这里必须添加。

tar:
-C DIR, --directory=DIR
切换到目录 DIR

-T FILE, --files-from=FILE
从 FILE 中获取要提取或创建的名称
-
上面的 FILE 是标准输入,来自管道


其他解决方案的评论

与 @aross 描述的解决方案相同可能会达到相同的结果。
这里和那个解决方案的区别在于哪个工具正在递归。如果你让 find 去做这个工作,每个文件路径名都需要经过管道。它还会发送所有的目录名,而 tar 会忽略或添加为空的目录,接着添加每个目录中的所有文件。如果从 find 读取的文件中有意外的输出,tar 将无法知道或关心发生了什么。
但是,通过进一步的检查,例如处理来自 find 的错误流,它可能是一个需要对文件进行许多选项和筛选的好解决方案。
我更喜欢将递归放在 tar 上,这似乎是更简单、更稳定的解决方案。
在我的复杂目录结构中,当 tar 不报告错误时,我感觉压缩文件是完整的。

另一种使用find的解决方案,由@serendrewpity提出,看起来不错,但是在文件名中包含空格时会失败。区别在于由$()子shell提供的find输出被空格分隔。可能可以使用printf添加引号,但它会进一步复杂化语句。

没有必要先进入my_directory然后返回,同时在tar路径中使用../my_archive.tar,因为TAR有-C DIR--directory=DIR命令,专门用于此目的。

使用.(点)将包括点

使用*将让shell提供输入文件列表。可能可以使用shell选项来包括点文件。但这很复杂。命令必须在允许的shell中执行。启用和禁用必须在tar命令之前和之后进行。如果未来归档的根目录包含太多文件,则将失败。

最后一点也适用于所有不使用管道的解决方案。

大多数解决方案都会创建一个目录,其中包含文件和目录。这几乎从来不是所期望的。


你可以在“find”命令后加上“2>/dev/null”,这样就可以确保只有文件名和路径被输出。 - aross
请解释一下。我不明白那应该如何实现。你的意思是要过滤掉错误,比如没有权限访问的文件吗?
  1. 我想看到错误信息。如果我预期不包括所有文件,则宁愿使用命令来排除这些文件。
  2. 管道符 | 只对标准输出起作用。任何打印到标准错误输出的内容都将无法传递给管道符右侧的 tar ,但默认会被打印到控制台上。
- papo
过滤掉错误是个好主意,我不确定 stderr 是否也会被管道传输...但是这样就没有问题了,对吧? - aross
除非你的意思是文件读取出错了...这意味着你有一个没有权限的文件。这很不寻常,但是有可能。在这种情况下,你可以在tar命令中添加“--ignore-failed-read”。 - aross

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接