Find files and tar them (with spaces)

LinuxFindBackupTar

Linux Problem Overview


Alright, so simple problem here. I'm working on a simple back up code. It works fine except if the files have spaces in them. This is how I'm finding files and adding them to a tar archive:

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

The problem is when the file has a space in the name because tar thinks that it's a folder. Basically is there a way I can add quotes around the results from find? Or a different way to fix this?

Linux Solutions


Solution 1 - Linux

Use this:

find . -type f -print0 | tar -czvf backup.tar.gz --null -T -

It will:

  • deal with files with spaces, newlines, leading dashes, and other funniness
  • handle an unlimited number of files
  • won't repeatedly overwrite your backup.tar.gz like using tar -c with xargs will do when you have a large number of files

Also see:

Solution 2 - Linux

There could be another way to achieve what you want. Basically,

  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
    

Solution 3 - Linux

Try running:

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

Solution 4 - Linux

Why not:

tar czvf backup.tar.gz *

Sure it's clever to use find and then xargs, but you're doing it the hard way.

Update: Porges has commented with a find-option that I think is a better answer than my answer, or the other one: find -print0 ... | xargs -0 ....

Solution 5 - Linux

If you have multiple files or directories and you want to zip them into independent *.gz file you can do this. Optional -type f -atime

find -name "httpd-log*.txt" -type f -mtime +1 -exec tar -vzcf {}.gz {} \;

This will compress

httpd-log01.txt
httpd-log02.txt

to

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

Solution 6 - Linux

Would add a comment to @Steve Kehlet post but need 50 rep (RIP).

For anyone that has found this post through numerous googling, I found a way to not only find specific files given a time range, but also NOT include the relative paths OR whitespaces that would cause tarring errors. (THANK YOU SO MUCH STEVE.)

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

  2. -name "*.pdf" look for pdfs (or any file type)

  3. -type f type to look for is a file

  4. -mtime 0 look for files created in last 24 hours

  5. -printf "%f\0" Regular -print0 OR -printf "%f" did NOT work for me. From man pages:

> This quoting is performed in the same way as for GNU ls. This is not the same quoting mechanism as the one used for -ls and -fls. If you are able to decide what format to use for the output of find then it is normally better to use '\0' as a terminator than to use newline, as file names can contain white space and newline characters.

  1. -czvf create archive, filter the archive through gzip , verbosely list files processed, archive name

Edit 2019-08-14: I would like to add, that I was also able to use essentially use the same command in my comment, just using tar itself:

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

Needed --ignore-failed-read in-case there were no new PDFs for today.

Solution 7 - Linux

Why not give something like this a try: tar cvf scala.tar `find src -name *.scala`

Solution 8 - Linux

Another solution as seen here:

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

Solution 9 - Linux

The best solution seem to be to create a file list and then archive files because you can use other sources and do something else with the list.

For example this allows using the list to calculate size of the files being archived:

#!/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

Solution 10 - Linux

Big warning on several of the solutions (and your own test) :

When you do : anything | xargs something

xargs will try to fit "as many arguments as possible" after "something", but then you may end up with multiple invocations of "something".

So your attempt: find ... | xargs tar czvf file.tgz may end up overwriting "file.tgz" at each invocation of "tar" by xargs, and you end up with only the last invocation! (the chosen solution uses a GNU -T special parameter to avoid the problem, but not everyone has that GNU tar available)

You could do instead:

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

Proof of the problem on 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

 

Note: that behavior of xargs is a well know diccifulty, and it is also why, when someone wants to do :

find .... | xargs grep "regex"

they intead have to write it:

find ..... | xargs grep "regex" /dev/null

That way, even if the last invocation of grep by xargs appends only 1 filename, grep sees at least 2 filenames (as each time it has: /dev/null, where it won't find anything, and the filename(s) appended by xargs after it) and thus will always display the file names when something maches "regex". Otherwise you may end up with the last results showing matches without a filename in front.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCaleb KesterView Question on Stackoverflow
Solution 1 - LinuxSteve KehletView Answer on Stackoverflow
Solution 2 - LinuxerrorproneView Answer on Stackoverflow
Solution 3 - LinuxgsteffView Answer on Stackoverflow
Solution 4 - LinuxWarren PView Answer on Stackoverflow
Solution 5 - LinuxKalibur xView Answer on Stackoverflow
Solution 6 - Linuxuser3472383View Answer on Stackoverflow
Solution 7 - LinuxFrank EgginkView Answer on Stackoverflow
Solution 8 - Linuxtommy.carstensenView Answer on Stackoverflow
Solution 9 - LinuxNuxView Answer on Stackoverflow
Solution 10 - LinuxOlivier DulacView Answer on Stackoverflow