2

I am writing a script for backup like this:

backup.sh:

dir="$1"
mode="$2"
delta="$3"

for file in "$dir/backup."*".$mode.tar.gz"; do
    [ "$file" -nt "$ref" ] && ref="$file"
done

if [ "$delta" = "true" ]; then
    delta_cmd=-N "'$ref'"
fi

backup_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz"

case "$mode" in
    config)
        tar -cpzvf "$backup_file" $delta_cmd \
            /etc \
            /usr/local
            ;;
    # still other modes here...
esac

I want to pass a single variable $delta_cmd to the tar command so that it tars all files or only delta files since last backup depending on the value of $delta.

The above code creates an error message and does not tar the delta files correctly if $delta is set to true. How to fix it?

P.S: The script would better be POSIX compatible.

6
  • 1
    It's possible to make this POSIX compatible, but only by either introducing security vulnerabilities (using eval) or overwriting $@ (which is quite not compatible with the use of a delta_cmd string) -- which would mean encapsulating the code in a function if you don't want to override the script-global value for that array. See BashFAQ #50 for a description of why string variables (as opposed to arrays) can't be safely used to store argument lists or commands, at mywiki.wooledge.org/BashFAQ/050 Commented Aug 1, 2016 at 19:05
  • Could you explain a bit more about the overwriting $@ way? Commented Aug 1, 2016 at 19:19
  • 1
    Added an answer. BTW, there's quite a lot in this code that could afford to be cut down to create a proper MCVE (minimal, complete, verifiable example; see stackoverflow.com/help/mcve) -- I'm very much hesitant to include content in my answer that includes such bad practices as parsing ls, but a better-practices replacement for that code is also somewhat outside the scope of the question at hand. Commented Aug 1, 2016 at 19:23
  • 1
    ...that said, please see BashFAQ #3 at mywiki.wooledge.org/BashFAQ/003 for guidance on a best-practices way to find the newest, or oldest, file in a directory. (We also have answers in the StackOverflow knowledge base about using GNU find to efficiently and -- unlike ls -- robustly find the most or least recent N files). Commented Aug 1, 2016 at 19:24
  • Thank you. I reduced the script in problem. Commented Aug 2, 2016 at 5:54

2 Answers 2

1

As a POSIX-compliant approach, consider:

set --                  # clear $@
if [ -f "$ref" ]; then
  set -- "$@" -N "$ref" # add -N "$ref" to $@
fi

tar ... "$@" ...        # expand $@ into command line

To put this all in context, and protect the main argument list against overwrite, might look like:

#!/bin/sh

main() {
    # if current shell supports "local", prevent variables from leaking
    # ...some "POSIX" shells, such as ash, will be fine with this.
    local dir mode delta target_file backup_file 2>&1 ||:

    dir=$1
    mode=$2
    delta=$3

    set -- # clear $@

    for file in "$dir/backup."*".$mode.tar.gz"; do
        [ "$file" -nt "$ref" ] && ref="$file"
    done

    if [ "$delta" = "true" ]; then
        set -- "$@" -N "$ref"
    fi

    target_file="$dir/backup.$(date +%Y%m%d-%H%M%S).$mode.tar.gz"

    case "$mode" in
        config)
            tar -cpzvf "$target_file" "$@" \
                /etc \
                /usr/local
            ;;
        # still other modes here...
    esac
}

main "$@"
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you. This is helpful. If we put the code fragment to a function in sub-shell such as backup () ( ... ) it would not overwrite $@ would it?
correct, it would only overwrite the $@ for that function. You actually don't need backup() ( ... ), backup() { ... } would suffice, since a function is in a different stack frame and so has its own argument list.
Note that there had been an error in the original post which was corrected: target_file should be backup_file.
1

You should be using BASH arrays for storing partial/full command lines:

#!/bin/bash

DIR=/home/sysop/backup
mode=main
delta=false

REF=$(ls -t "$DIR"/system.*.$mode.tar.gz "$DIR"/system.*.$mode-delta.tar.gz 2>/dev/null | head -n 1)
REF=$(readlink -f "$REF")

if [ "$delta" = true ]; then
    delta_cmd=(-N "$REF")
    delta_suffix=("-delta")
fi

target_file="$DIR/system.$(date +%Y%m%d-%H%M%S).$mode$delta_suffix.tar.gz"

tar -cpzvf "$target_file" "${delta_cmd[@]}" \
    /etc \
    /usr/local \
    /var/log \
    /var/spool \
    /home/*/logs

I would also suggest avoiding parsing of ls command's output in your script.

11 Comments

It does not seem to work POSIX-ly due to syntax error. I also tried using bash, but the result is error as previously if $delta=true.
This script should run on BASH. Can you tell me what is exact error while using BASH?
Oh you have misplaced quoted in array assignment. Note that I have delta_cmd=(-N "$REF") but you are showing delta_cmd=(-N "'$ref'")
You are not copying correctly from my answer. I have "${delta_cmd[@]}" but you have $delta_cmd.
You are right. After replacing $delta_cmd to "${delta_cmd[@]}", it works now.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.