找到文件并压缩它们(包含空格)

116

好的,这里有一个简单的问题。我正在编写一个简单的备份代码。它能正常工作,但如果文件名中有空格就会出现问题。这是我查找文件和将它们添加到tar归档文件的方式:

find . -type f | xargs tar -czvf backup.tar.gz 

问题出现在文件名中有空格时,因为tar会将其认为是一个文件夹。基本上,我是否可以在find的结果周围添加引号?或者有其他方法来解决这个问题吗?


12
使用 find ... | xargs ... 命令的最佳方法是每个命令都使用 -print0/-0 参数: find -print0 ... | xargs -0 ...。 这将使文件名由空字符分隔,这意味着您可以在文件名中包含空格、换行符或其他奇怪的字符,并且此命令仍能正常工作。 - porges
8
当你有大量文件时,使用xargs和tar这种方式存在问题。xargs将反复调用tar -c,这会不断覆盖你的归档文件,并导致你无法获得所有你期望的文件。请参见这个更详细的解释和下面的我的回答 - Steve Kehlet
10个回答

228
请使用以下内容:
find . -type f -print0 | tar -czvf backup.tar.gz --null -T -

它将会:

  • 处理带有空格、换行符、前导破折号和其他奇怪字符的文件
  • 处理无限数量的文件
  • 不会像使用 tar -cxargs 时在拥有大量文件时反复覆盖你的备份.tar.gz 文件

另请参见:


1
如果您想先通过sed几次来管道化查找,您会如何处理呢?例如:find . -print0 | sed /backups/d | tar... - Brad Parks
10
请注意,如果有多个条件,则需要添加括号。否则,“-print0”仅适用于最后一个表达式。例如,find . \( -type f -o -name '*.c' \) -print0 | ... - nimrodm
1
为了好玩,这里有一个使用cygwin的Windows版本:c:\cygwin\bin\find . -regextype posix-egrep -regex '.*(sln^|vcxproj^|filters)$' -print0 | c:\cygwin\bin\tar -cvf MS_Projects.tar --null -T - - Jon
1
@Steve能否请解释一下tar命令末尾的“-”选项。我在GNU tar的man手册中找不到它。 - shaffooo
当然,这是传递给“-T”的一个参数,它的意思是从标准输入读取文件名:如果你将单破折号作为“--files-from”的文件名(即指定--files-from=-或-T -),那么文件名将从标准输入中读取。 - Steve Kehlet
显示剩余2条评论

17

有另一种实现你想要的方式。基本上,

  1. Use the find command to output path to whatever files you're looking for. Redirect stdout to a filename of your choosing.
  2. Then tar with the -T option which allows it to take a list of file locations (the one you just created with find!)

    find . -name "*.whatever" > yourListOfFiles
    tar -cvf yourfile.tar -T yourListOfFiles
    

这里有一个关于如何处理文件名中包含换行符的答案:https://superuser.com/a/513319/151261 - tommy.carstensen
这个答案对我来说是正确的,因为在将列表输入到tar之前,我需要对其进行编辑。tar的第一个man页面似乎提供了可用选项的概述(所以我就停在那里了),但实际上还有很多选项。 - undefined

8

请尝试运行以下命令:

    find . -type f | xargs -d "\n" tar -czvf backup.tar.gz 

7

为什么不:

tar czvf backup.tar.gz *

使用find和xargs确实很聪明,但你是以一种较困难的方式来做。

更新:Porges已经发表评论,提供了一个更好的答案。它比我之前给出的答案或其他答案更好:find -print0 ... | xargs -0 ....


我的完整代码将仅备份在过去一天内修改的项目。由于这是每日备份,我不希望有重复的信息以节省文件大小(我还有每15天的完整备份)。 - Caleb Kester
为了让这个问题更好地适用于SO,我会提出一个关于“可靠地使用find、xargs和tar”的问题。你的标题和问题并没有明确指出你需要find和xargs,但实际上你确实需要它们。 - Warren P
如果文件列表太长,xargs ... tar c ... 将覆盖第一个创建的归档文件,并且 xargs 将再次执行 tar!为了避免覆盖,您可以使用 xargs -x,但是这样可能会导致归档文件不完整。另一种选择是先使用 tar c ...,然后可能反复使用 tar r ...。(这是我对可靠性的贡献 :) - pabouk - Ukraine stay strong

4
如果您有多个文件或目录,并希望将它们压缩成独立的 *.gz 文件,您可以这样做。可选参数 -type f -atime
find -name "httpd-log*.txt" -type f -mtime +1 -exec tar -vzcf {}.gz {} \;

这将会压缩内容。
httpd-log01.txt
httpd-log02.txt

to

httpd-log01.txt.gz
httpd-log02.txt.gz

3

我想在@Steve Kehlet的帖子中添加评论,但需要50个声望(RIP)。

对于通过众多谷歌搜索找到此帖子的任何人,我找到了一种方法,不仅可以在给定时间范围内找到特定文件,而且还可以不包括导致压缩错误的相对路径或空格。(非常感谢Steve。)

find . -name "*.pdf" -type f -mtime 0 -printf "%f\0" | tar -czvf /dir/zip.tar.gz --null -T -
  1. . 相对目录

  2. -name "*.pdf" 查找pdf(或任何文件类型)

  3. -type f 查找的类型为文件

  4. -mtime 0 查找创建时间在过去24小时之内的文件

  5. -printf "%f\0" 普通的-print0-printf "%f" 对我无用。从man页中得知:

这种引用方式与GNU ls相同。这不是与-ls和-fls使用的引用机制相同。如果您能决定find输出使用的格式,通常最好使用'\0'作为终止符而不是使用换行符,因为文件名可能包含空格和换行符。

  1. -czvf 创建档案,将档案过滤通过gzip,详细列出处理的文件,档案名称

编辑2019-08-14: 我还想补充一点,我也可以在我的评论中使用基本上相同的命令,只是直接使用tar本身:

tar -czvf /archiveDir/test.tar.gz --newer-mtime=0 --ignore-failed-read *.pdf

如果今天没有新的PDF文件,需要使用--ignore-failed-read


2
为什么不试试这样做:tar cvf scala.tar `find src -name *.scala`

2

这里还有另一种解决方案,可以参考这里

find var/log/ -iname "anaconda.*" -exec tar -cvzf file.tar.gz {} +

1
最好的解决方案似乎是创建一个文件列表,然后归档文件,因为你可以使用其他来源并对列表进行其他操作。
例如,这允许使用列表计算正在归档的文件的大小:
#!/bin/sh

backupFileName="backup-big-$(date +"%Y%m%d-%H%M")"
backupRoot="/var/www"
backupOutPath=""

archivePath=$backupOutPath$backupFileName.tar.gz
listOfFilesPath=$backupOutPath$backupFileName.filelist

#
# Make a list of files/directories to archive
#
echo "" > $listOfFilesPath
echo "${backupRoot}/uploads" >> $listOfFilesPath
echo "${backupRoot}/extra/user/data" >> $listOfFilesPath
find "${backupRoot}/drupal_root/sites/" -name "files" -type d >> $listOfFilesPath

#
# Size calculation
#
sizeForProgress=`
cat $listOfFilesPath | while read nextFile;do
    if [ ! -z "$nextFile" ]; then
        du -sb "$nextFile"
    fi
done | awk '{size+=$1} END {print size}'
`

#
# Archive with progress
#
## simple with dump of all files currently archived
#tar -czvf $archivePath -T $listOfFilesPath
## progress bar
sizeForShow=$(($sizeForProgress/1024/1024))
echo -e "\nRunning backup [source files are $sizeForShow MiB]\n"
tar -cPp -T $listOfFilesPath | pv -s $sizeForProgress | gzip > $archivePath

这个有一行代码可以解决吗? - Robino

0

对于几个解决方案(包括您自己的测试)有一个重要的警告:

当您执行:anything | xargs something

xargs 将尝试在“something”之后“尽可能多地添加参数”,但是这样您可能会得到多个“something”的调用。

因此,您的尝试:find ... | xargs tar czvf file.tgz 可能会在每次xargs调用tar时覆盖“file.tgz”,最终只保留最后一次调用!(所选解决方案使用GNU -T特殊参数来避免该问题,但并非每个人都有可用的GNU tar)

您可以改为:

find . -type f -print0 | xargs -0 tar -rvf backup.tar
gzip backup.tar

在Cygwin上出现问题的证明:

$ mkdir test
$ cd test
$ seq 1 10000 | sed -e "s/^/long_filename_/" | xargs touch 
    # create the files
$ seq 1 10000 | sed -e "s/^/long_filename_/" | xargs tar czvf archive.tgz
    # will invoke tar several time as it can'f fit 10000 long filenames into 1
$ tar tzvf archive.tgz | wc -l
60
    # in my own machine, I end up with only the 60 last filenames, 
    # as the last invocation of tar by xargs overwrote the previous one(s)

# proper way to invoke tar: with -r  (which append to an existing tar file, whereas c would overwrite it)
# caveat: you can't have it compressed (you can't add to a compressed archive)
$ seq 1 10000 | sed -e "s/^/long_filename_/" | xargs tar rvf archive.tar #-r, and without z
$ gzip archive.tar
$ tar tzvf archive.tar.gz | wc -l
10000 
  # we have all our files, despite xargs making several invocations of the tar command

 

请注意:xargs的行为是一个众所周知的困难,这也是为什么当有人想要执行以下操作时:
find .... | xargs grep "regex"

他们只能写它:
find ..... | xargs grep "regex" /dev/null

这样,即使xargs最后一次调用grep只附加了一个文件名,grep也会看到至少2个文件名(因为每次它都有:/dev/null,在那里它找不到任何东西,以及在其后由xargs附加的文件名),因此当匹配“regex”时总是显示文件名。否则,您可能会出现最后的结果显示匹配项而没有文件名。

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