0

I am attempting to use the [[ ]] operator in Bash and failing badly. My script is:

#!/bin/bash
# Test driver for the ProtectDatae script 
set -o xtrace
# [[ -z "$1" ]] || source="$1" && source=""
if [[ -z "%1" ]]
then
  source=""
else
  source=$1
fi
[[ -z "$2" ]] || target="$2" && target="" # This does not work
[[ -z "$3" ]] || sourceBackup="$3" && sourceBackup="/tmp/sourcebackup" # This does not work
source cmd.sh  # Get a function to run a Linux command
if [[ -e "$sourceBackup" && "$sourceBackup" -ne "" ]]
then
  # If a source backup directory is specified then get rid of any old directory and make a new backup
  # and verify it. If OK, make the source directory be the source backup directory
  # otherwise work directly in the source directory
  if [[ -e "$sourceBackup" ]]
  then
    cmd "sudo rm -R $sourceBackup" "empty backup directory $sourceBackup failed"
    cmd "cp -apu $source $sourceBackup" "backup home directory"
    cmd "diff -aprN ~/$source/* $sourceBackup" "bad backup in $sourceBackup"
    source="$sourceBackup"
 fi
fi

exit 0

My command invocation is ./TestProtectData.sh "~" /tmp/jfghome /tmp/jfgbackup

The result of xtrace is:

+ source='~'
+ [[ -z /tmp/jfghome ]]
+ target=/tmp/jfghome
+ target=""
+ [[ -z /tmp/jfgbackup ]]
+ sourceBackup=/tmp/jfgbackup
+ sourceBackup=/tmp/sourcebackup
+ source cmd.sh
+ [[ -e /tmp/sourcebackup ]]
+ exit 0

What happens with the following line is the error. Both alternatives appear to be executed and the variable winds up being set incorrectly:

[[ -z "$2" ]] || target="$2" && target=""

I have tried both orders for && and || and they both give the same result with the variable target set to blank. On the next line, a similar thing happens with the variable sourceBackup set to the second alternative, and both alternatives appear to be executed. If I use the if then construct it works. What am I doing wrong?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Jonathan
  • 2,635
  • 3
  • 30
  • 49
  • Is `%1` a typo? – Cyrus Dec 12 '22 at 01:41
  • 2
    I suggest to use `if ...; then ...; else ...; fi` – Cyrus Dec 12 '22 at 01:56
  • I am aware that construct works. I am trying to learn how to use [[ ]] which is more compact. – Jonathan Dec 12 '22 at 02:02
  • 2
    @Jonathan The basic problem here is that you're trying to use `&&` and `||` instead of `if ... then ... else ... fi`, and they're not equivalent. They can be close, but there are weird potential problems (like [this one](https://stackoverflow.com/questions/61217624/cant-increment-variable-in-bash-shorthand-if-else-condition)), and constructs with weird potential problems are things you should avoid. Also, in the case of default parameter values, there's a much better method: `sourceBackup="${3:-/tmp/sourcebackup}"` (see [this answer](https://stackoverflow.com/questions/9332802#9333006)). – Gordon Davisson Dec 12 '22 at 02:39

2 Answers2

1

What am I doing wrong?

Your intended logic doesn't match the bash constructs you're using. This line:

[[ -z "$2" ]] || target="$2" && target="" # This does not work

Breaks down to mean if 2 is not empty set target to $2. If that command succeeds, set target to "". The command to the left of && will always succeed - either the -z test succeeds or the target="$2" succeeds. Thus target="" always runs at the end.

You can use if ... ; then ...; else ...; fi or you can look at these ways to effect a ternary operator in bash, including:

#!/bin/bash -e
[[ -z "$3" ]] && sourceBackup="/tmp/sourcebackup" || sourceBackup="$3"
echo $sourceBackup
% ./t.sh 1 2 3                                                                                                                                                            
3

Here, if -z test succeeds we set sourceBackup to the default. If the test fails, $3 is not null and we set sourceBackup to $3.

To reiterate, this is not exactly the same as a tertiary operator. But if you get the order correct, it will work.

erik258
  • 14,701
  • 2
  • 25
  • 31
  • erik258, Your example```[[ -z "$3" ]] && sourceBackup="/tmp/sourcebackup" || sourceBackup="$3"``` does not seem to work. It gives the second alternative when $3 is not empty. – Jonathan Dec 12 '22 at 02:16
  • Is that not what you want? You'll notice the first alternative is setting to the default if $3 *is* empty. – erik258 Dec 12 '22 at 02:20
  • erik258, thanks. I mixed things up when trying it. – Jonathan Dec 12 '22 at 02:31
1

A plain assignement (foo=bar) always sets the status code to 0, so after target has been set to $2, it is immediately after set to empty. I would start by turning the logic inside out:

target= # Set to empty string
[[ -z $2 ]] && target=$2

However, this is redundant. You could easier simply just write

target=$2

without doing any [[...]]-test. If the second parameter is empty, target will be empty as well. If it is not empty, target will get that value.

There is one point to consider: In case you decide to turn on set -u, to catch uninitialized variables, a target=$2 would abort the script if there is no second parameter. Therefore, you could also write

target=${2:-}

which tells bash that a missing parameter is OK and should be treated as a null string.

Even though it is redundant, if you do not turn on -u, using ${2:-} shows your intent explicitly, and makes your program more maintainable.

user1934428
  • 19,864
  • 7
  • 42
  • 87