Run commands on same line as [[ ]] operator - bash

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?

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.

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.

Related

Checking if a branch exists in Jenkins shell script

I am having a trouble checking if a branch exists in a Jenkins job.
My shell scripts is as follows:
set +e
BRANCH_EXISTS=$(git ls-remote --quiet | grep -w ${BRANCH_NAME})
if [[ -z $"{BRANCH_EXISTS}" ]]
then
echo "Branch does not exist"
else
echo "Branch exists"
fi
Following is a log:
07:36:26 + BRANCH_EXISTS='4cbe2d1776db4dc263a1e7884e35da49a0a6f309 refs/heads/equalizer_fe_testing'
07:36:26 + [[ -z {BRANCH_EXISTS} ]]
07:36:26 + echo 'Branch exists'
07:36:26 Branch exists
...
07:36:27 + BRANCH_NAME=test/fe-testing_staging1
07:36:27 ++ grep -w test/fe-testing_staging1
07:36:27 ++ git ls-remote --quiet
07:36:28 + BRANCH_EXISTS=
07:36:28 + [[ -z {BRANCH_EXISTS} ]]
07:36:28 + echo 'Branch exists'
07:36:28 Branch exists
07:36:29 Finished: SUCCESS
As you can see, in one case I check for a branch that does not exist. But still the if condition passes. I have tried with if [[ -n $"{BRANCH_EXISTS}" ]] as well but still gets the same result.
What am I doing wrong here?
You are incorrectly using the test operator to check if string is empty. In most shells, variable interpolation happens with ${var} and not {var} or $"{var}"
$ var=foo
$ [[ -z $var ]] && echo ok
$ [[ -z $var ]] || echo ok
ok
$ var=
$ [[ -z $var ]] && echo ok
ok
$ [[ -z var ]] && echo ok # does not assert, because literal 'var' is not empty
When you are doing [[ -z {var} ]] you are not interpolating var, but comparing against a literal string {var} which is never empty.
Change your conditional to [[ -z "${BRANCH_EXISTS}" ]] to make it work as expected.
As Inian already explained in his answer, $"{BRANCH_EXISTS}", but "${BRANCH_EXISTS}" would be correct. However, there is more to it:
In your case, the double quotes are not necessary, because no word splitting occures here after parameter expansion (since you are inside a [[....]]. Hence you can write simply [[ -z $BRANCH_EXISTS ]].
However, if the branch does not exist, grep will return a non-zero exit code, and the program will be aborted (due to your set -e. Therefore, the then branch of your conditional won't ever be executed, even if you would fix the conditional.

Allowing a wget to run as part of a command line parameter

I'm writing a little script that takes several command line arguments and substitutes their values into some files.
I have a requirement where the user can either specify a file on their machine, or fetch it over http(s), but the problem is my script eats up the wget as a parameter, and doesn't actually execute it.
Here's what I'm using to parse the arguments:
while [[ "$#" -gt 0 ]] ; do
if [[ "$1" == '--ip-address' ]] ; then
shift
ip_address="$1"
fi
if [[ "$1" == '--hostname' ]] ; then
shift
hostname="$1"
fi
shift
done
What I'm looking for is something like
script.sh --file wget http://foo.bar/file.txt and it would first download the file and then pass it as a parameter.
What about something like:
if [[ "$1" == '--file' ]] ; then
shift
filename="$1"
if [ `echo $filename|grep '://'` != "" ]; then
wget --no-check-certificate -O /tmp/file "$filename"
filename=/tmp/file
fi
fi

Checking and appropriately correcting the arguments as the input for the script from command line

I would like to ask you and request for the help with this particular part of the script - I would like to make it more compact as I feel it is too long, or there might me a shorter syntax alternatives...
This part is cut from my script for creation of X amount of files with descending date (1 day jumps, so each file is 1 day older that previous), which is already 3x longer than the whole "implementation" part.
This part's purpose is to maintain, check and correct (appropriately) the arguments defined with the script execution, which looks like
./script.sh X X
whereas script expects two arguments (number indicating amount of files to create and the path to the folder, where it is supposed to create those0:
- if there is just 1 it assumes the user wants to create files in the present folder, therefore it checks, if the one included argument is a number and if it is it will add output from pwd to the path (in this case to not have some sort of "cross-mount" accident with the system variable PATH conveniently named PATHX), so there will be two arguments at the end anyway
- if there are more than 2, it will terminate the script
If there are 2 arguments, it performs additional "tasks:
1. checks if there are just two numbers, if so, script ends
2. checks if there are just two words/letters, if so, script ends
3. if there are, let's say, just two dots as arguments, it will end anyway as there is one if, which always will need one argument to be just number
4. after first steps this will save the arguments to the variables (it is necessary as I have had some problems in the implementation part), this step even coves any possible combination of the positions of the arguments (it does not matter now, if the amount is first or second; same for the path)
5. this is a very last step, which just (for any case) covers the case, the path included has not been inserted with the slash in front (as this is mandatory for bash to recognise it indicates an absolute path) or at the end (important for the touch command in the implementation path as the command looks like "touch -t *TIMESTAMP* "$PATHXfile$i""); together with it it does provide an appropriate action, if the path is defined with ".","..","./","../" - if there would be a full path, not relative one
if [[ $# -eq 2 ]]
then
if [[ $1 == ?(-)+([0-9]) ]] && [[ $2 == ?(-)+([0-9]) ]]
then
echo "Invalid arguments. Did you include a slash at the start of the path (if the file name consists only of the numbers)? Well, try it again. Terminating script..."
exit
elif [[ $1 == ?(-)+([a-z]) ]] && [[ $2 == ?(-)+([a-z]) ]]
then
echo "Only characters in the arguments. Is this some sort of a joke? If you have tried some sick hexadecimal format of number (just A-F), it will not work, mate. Terminating script..."
exit
fi
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$2
else
AMOUNT=$2
if [[ $AMOUNT != ?(-)+([0-9]) ]]
then
echo "No argument with number/numbers-only as an amount of files to create. Terminating script..."
exit
fi
PATHX=$1
fi
else
if [[ $# -eq 1 ]]
then
if [[ $1 == ?(-)+([0-9]) ]]
then
AMOUNT=$1
PATHX=$(pwd)
else
echo "Please, include the argument with an amount of files to create. Terminating script..."
exit
fi
else
echo "2 Arguments are expected. Terminating script..."
exit
fi
fi
if [[ $PATHX != /* ]]
then
if [[ "$PATHX" == "." ]] || [[ "$PATHX" == ".." ]] || [[ "$PATHX" == "./"* ]] || [[ "$PATHX" == "../"* ]]
then
true
else
PATHX=$(echo "/$PATHX")
fi
fi
if [[ $PATHX != */ ]]
then
PATHX=$(echo "$PATHX/")
fi
Excuse this formatting, but without adding the description to the code sample it is just a mess of text...
Anyway, thank you, Guys, for any input.
It may be reduced to:
[[ $# -gt 2 ]] && { echo "Incorrect number of arguments"; exit 1; }
[[ $1 == ?(-)+([0-9]) ]] || { echo "First argument is not a number"; exit 2; }
[[ $2 == ?(-)+([0-9]) ]] && { echo "Both arguments are numbers"; exit 3; }
[[ $2 == +([a-z]) ]] || { echo "File name must be only letters"; exit 4; }
[[ ! -d $2 ]] && { echo "Path does not exist"; exit 5; }
n=$1
p=${2:-$PWD} # Use the PWD if path is missing.
if [[ $p != */ ]]; then
echo "Missing trailing slash; correcting";
p+=/
fi
if [[ $p != /* ]]; then
echo "Missing leading slash; correcting";
[[ $p == #(.|..|./*|../*) ]] || p=/"$p"
fi

Bash substitution: log when variable is not set

I use bash substitutions to give neat one-line validation for input, e.g.:
#!/bin/bash
export PARAM1=${1?Error, please pass a value as the first argument"}
# do something...
In some cases though, I want to only log a message when something is unset and then continue as normal. Is this possible at all?
Maybe something along the lines of
test -n "$1" && export PARAM1="$1" || log "\$1 is empty!"
should do; here the test clause returns true if and only if $1 is non-empty.
For regular parameters (in bash 4 or later), you can use the -v operator to check if a parameter (or array element, as of version 4.3) is set:
[[ -v foo ]] || echo "foo not set"
bar=(1 2 3)
[[ -v bar[0] ]] || echo "bar[0] not set"
[[ -v bar[8] ]] || echo "bar[8] not set"
Unfortunately, -v does not work with the positional parameters, but you can use $# instead (since you can't set, say, $3 without setting $1).
(( $# >= 3 )) || echo "third argument not set"
Before -v became available, you would need to compare two default-value expansions to see if a parameter was unset.
[[ -z $foo && ${foo:-bar} == ${foo-bar} ]] && echo "foo is unset, not just empty"
There's nothing special about bar; it's just an arbitrary non-empty string.

bash - Possible to 'override' the test ([[)-builtin?

Is it possible to override Bash's test builtin? So that
[[ $1 = 'a' ]]
not just does the test but also outputs which result was expected when it fails? Something like
echo "Expected $1 to be a.'
EDIT
I know this is bad :-).
The test expression compound command does real short-circuiting that affects all expansions.
$ set -x
$ [[ 0 -gt x=1+1 || ++x -eq $(tee /dev/fd/3 <<<$x) && $(echo 'nope' >&3) ]] 3>&1
+ [[ 0 -gt x=1+1 ]]
++ tee /dev/fd/2
2
+ [[ ++x -eq 2 ]]
So yes you could do anything in a single test expression. In reality it's quite rare to have a test produce a side-effect, and almost never used to produce output.
Also yes, reserved words can be overridden. Bash is more lenient with ksh-style function definitions than POSIX style (which still allows some invalid names).
function [[ { [ "${#:1:${##}-1}" ]; }; \[[ -a -o -a -o -a ]] || echo lulz
Yet another forky bomb.
if function function if function if if \function & then \if & fi && \if & then \function & fi && then \function fi
Something like this?
if [[ $1 == 'a' ]]; then
echo "all right";
else
echo 'Expected $1 to be "a"'
fi
Anyway, what's the point of the test if you only expect one answer? Or do you mean that for debugging purposes?
[[ 'a' = 'a' ]] || echo "failed"
[[ 'b' = 'a' ]] || echo "failed"
failed

Resources