A mental note and short reminder on how to use the “test” command in bash if-loops or using the && and || operators for conditional scripting.
This is a short introduction - and personal reminder - on how to use the test
command.
The Google “Shell Style Guide”↗ prefers double [[ … ]]
over the older (but POSIX portable) single [ … ]
or literal test
and I won’t question this. 1
The test
command returns 0 (True) or 1 (False), which we can use conditionally in if-loops or using the && and || operators, and one could also simply examine the return value by displaying $?
e.g.
$ [[ "foo" != "bar" ]]; echo $?
0
$ [[ "foo" == "bar" ]]; echo $?
1
$ [[ "foo" == "foo" ]]; echo $?
0
$ [[ "foo" != "foo" ]]; echo $?
1
$ [[ 5 > 2 ]]; echo $?
0
There are tons of applications, but we will mostly use it to grep
for certain outputs or check variables
or for the existence of files.
Consult help test
for further details.
var=baz # this is our test input, i.e. [[ "foo" == $var ]] will return "False" (i.e. "1"): [[ "foo" == $var ]]; echo $? vs. [[ "baz" == $var ]]; echo $?
if [[ "foo" == $var ]]; then
echo "VAR equals foo"
elif [[ "bar" == $var ]]; then
echo "VAR equals bar"
else
echo "we got it all wrong and VAR equals something else"
fi
The use of echo
here is just a substitute for any other command that might be used here.
There is a more cunning, and cryptic or 31337, way of writing if-statements using the && and || operators.
&& and || are actually LOGICAL operators, i.e.
&& = AND
|| = OR
the general logic for scripting or chaining commands would be
"FOO ; BAR" # Run FOO and then BAR, regardless of success of FOO
"FOO && BAR" # Run BAR if FOO succeeded, i.e. returns "True"
"FOO || BAR" # Run BAR if FOO failed, i.e. returns "False"
"FOO &" # Run FOO in the background.
"FOO | BAR" # pipe the output of FOO into BAR for further processing
to substitute an if-else-loop with && and || use as follows
# the nifty way
[[ "foo" != "bar" ]] && echo "true, foo != bar" || echo "if false, obviously VAR must have been foo then as well"
# which is the same as
if [[ "foo" != "bar" ]]; then
echo "true, foo != bar"
else
echo "if false, obviously VAR must have been foo then as well"
fi
these operators can also be used to chain multiple tests/ conditions together into one test
e.g.
$ var=baz
# OR; is one or the other "True"
$ [[ "foo" == $var || "baz" == $var ]] && echo "true" || echo "false"
true
# AND; both must be "True"
$ [[ "foo" == $var && "baz" == $var ]] && echo "true" || echo "false"
false
For example, when I’m downloading, extracting and forwarding files based on a list of ISBNs and do not know whether I’ll find a package for each of the given ISBNs, then I need to know the failed ISBN, so that I do not include it within my extra metadata file, in which I only want to provide data for succeeded ISBNs. Obviously, I’m aware that I could include a test
at an earlier stage already, but this is my workshop;-)
ISBNLIST=ISBNs4NYU.txt
BASEDIR=$(pwd)
WORKDIR=$BASEDIR/ProQuest_Deposit_$(date +%F); mkdir $WORKDIR; cp $ISBNLIST $WORKDIR; cd $WORKDIR
# Download WebPDFs directly out of the PDW
for ISBN in $(cat $ISBNLIST); do
echo $ISBN
# getting "documentId"
DOCUMENTID=$(curl -s --proxy "http://proxy-URL:1337" https://product-data-warehouse-URL/publication/$ISBN | grep '"id"' | head -n1 | grep -oP '(?<="id" : ").+?(?=")')
# downloading ZIP
curl -s --proxy "http://proxy-URL:1337" https://product-data-warehouse-URL/document/$DOCUMENTID/zip -o $ISBN.zip
# unzip WebPDF only
unzip -q -j $ISBN.zip *$DOCUMENTID.pdf
# rename to ISBN.pdf
mv $DOCUMENTID.pdf $ISBN.pdf
# if WebPDF exists, then upload to target FTP
if [[ -f $ISBN.pdf ]]; then
curl -T $ISBN.pdf -u UN:PW ftp://ftp.data-recipient.com/upload/
echo "$ISBN" >> isbn_list_4_onix.txt
else echo "$ISBN" >> WebPDF_missing.txt
fi
# clean-up
rm $ISBN.zip $ISBN.pdf
done
cd $BASEDIR
I usually only need -f
, but again check help test
for other useful options like
File operators:
-d FILE True if file is a directory.
-e FILE True if file exists [regardless of type, e.g. file, directory, device etc.].
-f FILE True if file exists and is a regular file [and not a directory or device etc.].
-s FILE True if file exists and is not empty.
A -nt B Test if file A is newer than file B, by modification date.
A -ot B Test if file A is older than file B.
This check could also be negated, so that the outcome is “True” when the file does NOT exist:
if [[ ! -f $ISBN.pdf ]]; then
echo "$ISBN.pdf does not exist."
fi
For example, I want to prompt the user to specify a file, like an ISBN list, when there was no command line argument (i.e. $1
) given in the first place:
# check for input variable, if empty prompt for file in current directory, else move on with the given input variable
if [[ -z "$1" ]]; then
echo; echo "Please select ISBN list: (choose number)"; echo
select L in *; do test -n "$L" && break; echo ">>> Invalid Selection"; done
else
L=$1
fi
String operators:
-z STRING True if string is empty.
-n STRING True if string is NOT empty.
# nifty alternative
[[ -z "$var" ]] && echo "true, VAR is empty" || echo "false, VAR is Not empty"
This IBM tutorial on Bash test and comparison functions↗ is also very helpful.
to be continued …
[[ … ]]
reduces errors as no pathname expansion or word splitting takes place between [[
and ]]
. In addition, [[ … ]]
allows for regular expression matching, while [ … ]
does not. ↩︎
For attribution, please cite this work as
Schmalfuß (2017, Nov. 9). OS DataMercs: bash 'test' command. Retrieved from https://www.datamercs.net/posts/2017-11-09-bash-test-command/
BibTeX citation
@misc{schmalfuß2017bash, author = {Schmalfuß, Olaf}, title = {OS DataMercs: bash 'test' command}, url = {https://www.datamercs.net/posts/2017-11-09-bash-test-command/}, year = {2017} }