1

I am trying to write a shell scripts that will back up every file in a directory. In oder to get the files I use

FILENAMES="$(find -maxdepth 1 -not -path './.*' -type f)"

which returns something containing all the names of the files.

If none of the filenames contain spaces then using

tar --exclude='archive.tar.bz2' -cvpjf archive.tar.bz2 $FILENAMES

does the job and all is fine.

However if there is a file names "bad filename.txt" the above approach does not work and tar will complain that it cannot find "bad" of "filename.txt".

I tried to fix this by using an inline replacement as described here, but that does not work:

TOARCHIVE=${FILENAMES// /'\ '}

Basically what this does is to replace the spaces in the appropriate position, IF I echo it. But tar does not seem to see the inserted '\'. If try the replacement code with two slashes, such as '\\' or '\\ ' tar will see both slashes and again complain.

Another solution to similar problems I saw was to make sure everything is in quotes, but that does not work for me since TOARCHIVE contains the names of multiple files.

Nikolai
  • 21
  • 2
  • You may want to use nul separator https://www.gnu.org/software/tar/manual/html_node/nul.html - and pass `-print0` to find. – ideasman42 Aug 07 '17 at 11:28

5 Answers5

0

For characters replacement, i suggest you to use sed, like this :

sed "s/\ /\\/g" myfile.txt

This will replace all the spaces in myfile.txt by a \

Eos
  • 112
  • 1
  • 9
0

I would suggest not simply replacing ' ' with '\ ' if you want to correctly quote paths.

Either:

  • Use nul terminators (-print0 argument to find)
  • Quote the paths using printf.

While not exactly what your asking for, correct path quoting can be done using printf '%q', eg:

FILENAMES="$(
    find -maxdepth 1 -not -path './.*' -type f -print0 |
    xargs -0 printf '%q\n'
)"

This is useful because it means other strange characters in file paths wont cause issues later on.

If some tricky person runs touch "foo\ bar.txt" for example :)

ideasman42
  • 42,413
  • 44
  • 197
  • 320
0

One way to do it is to execute the tar utility from find itself:

find -maxdepth 1 -not -path './.*' -type f -exec tar --exclude='archive.tar.bz2' -cvpjf archive.tar.bz2 "{}" +
Sakis
  • 91
  • 3
0
FILENAMES="$(find -maxdepth 1 -not -path './.*' -type f -printf  "'"%f"'")"

Print each filename surrounded by single quotations.

Raman Sailopal
  • 12,320
  • 2
  • 11
  • 18
0

Don't store a list of file names in a regular parameter. Use an array parameter instead. You don't need find either; a simple glob will match the same files.

filenames=( * )  # assuming there are no subdirectories in the current directory

To exclude directories (and other non-regular files), use a shell loop:

filenames=()
for f in *; do
    [[ -f $f ]] && filenames+=("$f")
done

Once the array is populated, use it as

tar --exclude='archive.tar.bz2' -cvpjf archive.tar.bz2 "${filenames[@]}"

If you had a more complex call to find, I would simply pipe the null-terminated list of found file names directory to tar without storing them first (assuming GNU tar, anyway):

find ... -print0 | tar --null -T - --exclude 'archive.tar.bz2' -cvpjf archive.tar.bz2
chepner
  • 497,756
  • 71
  • 530
  • 681