9

I'm working on a script and I need to build the tar command dynamically.

Here are two examples to illustrate what I'm trying to do :

#!/bin/bash

TAR_ME="/tmp"

EXCLUDE=("/tmp/hello hello" "/tmp/systemd*" "/tmp/Temp*")
_tar="tar "`printf -- '--exclude="%s" ' "${EXCLUDE[@]}"`" -zcf tmp.tar.gz"
echo COMMAND: "${_tar}"
${_tar} "$TAR_ME"

echo -e "\n\nNEXT:\n\n"

EXCLUDE=("--exclude=/tmp/hello\ hello" "--exclude=/tmp/systemd*" "--exclude=/tmp/Temp*")
_tar="tar "`printf -- '%s ' "${EXCLUDE[@]}"`" -zcf test.tar.gz"
echo COMMAND: "${_tar}"
${_tar} "$TAR_ME"

I want to be able to use _tar as a command, I've been able to make it work with classic path, but I need it to work with spaces in folders' name. And every single time I got errors that look like :

COMMAND: tar --exclude="/tmp/hello hello" --exclude="/tmp/systemd*" --exclude="/tmp/Temp*"  -zcf tmp.tar.gz /tmp
tar: hello": Cannot stat: No such file or directory

COMMAND: tar --exclude=/tmp/hello\ hello --exclude=/tmp/systemd* --exclude=/tmp/Temp*  -zcf test.tar.gz 
tar: hello: Cannot stat: No such file or directory

Just one thing you need to know, I need my script to work on very old machines, meaning I can't use last bash features.

8
  • I believe the --exclude option can only accept a single string after it. You can have multiple --exclude statements though. Maybe try "--exclude=/tmp/hello --exclude=hello" Oops. Nevermind. I misunderstood.
    – Lewis M
    Oct 4, 2018 at 13:43
  • @LewisM I think OP want to exclude directory "/tmp/hello hello" (yes, with a space.
    – Archemar
    Oct 4, 2018 at 13:44
  • @ShellCode what about quoting all exclude, e.g. "--exclude=/tmp/hello hello"
    – Archemar
    Oct 4, 2018 at 13:45
  • Yeah. That's why I put the Oops statement later. :)
    – Lewis M
    Oct 4, 2018 at 13:46
  • How about putting eval in front of the execution?
    – jimmij
    Oct 4, 2018 at 13:52

2 Answers 2

15

Don't try to make an executable string. Instead build the arguments in an array and use that when calling tar (you are already using an array properly for EXCLUDE):

#!/bin/bash

directory=/tmp

exclude=( "hello hello" "systemd*" "Temp*" )

# Now build the list of "--exclude" options from the "exclude" array:
for elem in "${exclude[@]}"; do
    exclude_opts+=( --exclude="$directory/$elem" )
done

# Run tar
tar -cz -f tmp.tar.gz "${exclude_opts[@]}" "$directory"

With /bin/sh:

#!/bin/sh

directory=/tmp

set -- "hello hello" "systemd*" "Temp*"

# Now build the list of "--exclude" options from the "$@" list
# (overwriting the values in $@ while doing so):
for elem do
    set -- "$@" --exclude="$directory/$elem"
    shift
done

# Run tar
tar -cz -f tmp.tar.gz "$@" "$directory"

Note the quoting of $@ in the sh code and of both ${exclude[@]} and ${exclude_opts[@]} in the bash code. This ensures that the lists are expanded to individually quoted elements.

Related:

0
2
mix(){
        p=$1; shift; q=$1; shift; c=
        i=1; for a; do c="$c $q \"\${$i}\""; i=$((i+1)); done
        eval "${p%\%*}$c${p#*\%}"
}
mix 'tar % -zcf tmp.tar.gz' --exclude "/tmp/hello hello" "/tmp/systemd*" "/tmp/Temp*"

EXCLUDE=("/tmp/hello hello" "/tmp/systemd*" "/tmp/Temp*")
mix 'tar % -zcf tmp.tar.gz' --exclude "${EXCLUDE[@]}"

Extending the answer here. This doesn't rely on any bashisms, it will also work fine with debian's /bin/sh, and with busybox.

5
  • Thank you very much for your help, but I don't really like the eval, it's quite dangerous... Moreover, this code is quite hard to understand, don't you have something easier ? :/ The script will be distributed so I have to keep it as simple as possible...
    – ShellCode
    Oct 4, 2018 at 14:03
  • It's not dangerous. Run it with set -x. What exactly you don't understand?
    – user313992
    Oct 4, 2018 at 14:04
  • Also, read the original answer on stackoverflow. It includes a demo.
    – user313992
    Oct 4, 2018 at 14:10
  • It works quite well though... Waiting to see if anybody has a cleaner answer, otherwise I will accept yours. Maybe there is nothing wrong with that code, but every time I see an eval, I'm afraid the code could lead to command injection, that's why I try to avoid it
    – ShellCode
    Oct 4, 2018 at 14:13
  • I've updated the answer with a fix for indexes > 9. You can replace the eval with an echo to see what's actually getting (the eval doesn't see the filenames)
    – user313992
    Oct 4, 2018 at 14:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .