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.

Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232
ShellCode
  • 225
  • 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 04 '18 at 13:43
  • @LewisM I think OP want to exclude directory "/tmp/hello hello" (yes, with a space. – Archemar Oct 04 '18 at 13:44
  • @ShellCode what about quoting all exclude, e.g. "--exclude=/tmp/hello hello" – Archemar Oct 04 '18 at 13:45
  • Yeah. That's why I put the Oops statement later. :) – Lewis M Oct 04 '18 at 13:46
  • How about putting eval in front of the execution? – jimmij Oct 04 '18 at 13:52
  • Does your unspecified very old machine even have bash? Are you sure you're looking explicitly for a bash script or do you actually need "normal vanilla" shell? – pipe Oct 04 '18 at 15:35
  • Yep bash is ok, but it has to be compatible with old bash versions – ShellCode Oct 04 '18 at 18:22
  • @pipe The shell you mean is sh, the Bourne shell. But if by "normal vanilla" you mean most standard shell, it's posh, A shell implementing the current POSIX specification. – Volker Siegel Oct 04 '18 at 18:23

2 Answers2

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:

Kusalananda
  • 333,661
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.

  • 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 04 '18 at 14:03
  • It's not dangerous. Run it with set -x. What exactly you don't understand? –  Oct 04 '18 at 14:04
  • Also, read the original answer on stackoverflow. It includes a demo. –  Oct 04 '18 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 04 '18 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) –  Oct 04 '18 at 14:29