What is the most idiomatic way in Bash to test if no positional parameters are given? There are so many ways to check this, I wonder if there is one preferred way.
Some ways are:
((! $# )) # check if $# is 'not true'
(($# == 0)) # $# is 0
[[ ! $# ]] # $# is unset or null
For me, the classical way is:
[[ $# -eq 0 ]]
If you want it to be an error to have no positional parameters:
: ${#?no positional parameters}
will print "no positional parameters" to standard error (and exit a non-interactive shell) if $# is unset.
Otherwise, I'm not aware of any better options than one of the various methods of checking if $# is 0.
Use Sensible Semantics
The key is readability and the intent of your code. Unless you have a good reason to do otherwise, you probably just want to determine the length of the parameter list.
# Is length of parameter list equal to zero?
[ $# -eq 0 ]
However, you can certainly use any parameter expansion or comparison operator that expresses the intent of your code. There's no right way to do it, but you certainly may wish to consider whether the semantics of your test are portable.
Food for Thought
It isn't the conditional expression that's intrinsically important. What's important is why you want to know. For example:
set -e
foo="$1"
shift
# $2 is now $1 (but only if the line is reached)
echo "$1"
In this case, the length of the parameter list is never checked directly. The first parameter is simply assigned (even though it may be unset), and then the shell throws an error and exits when you try to shift the parameter list. This code says "I just expect the parameters to be there; I shouldn't have to check for them explicitly."
The point here is that you need to determine what your code is trying to express, and match the semantics of your tests and conditionals to express that as clearly as you can. There really is no orthogonal answer.
Here's a most logical way:
[ ! $# ]
It is based on a single rule:
[ ] # this returns 1
Well then,
[ ! ] # this returns 0
The rest is obvious:
$# is the special parameter that expands to a list of all positional parameters.
Test:
(It will work even if you throw a couple of empty strings ("" "" "") at it.)
if [ ! $# ]; then
printf 'Note: No arguments are given.'
fi
I prefer using the fact that if there are no positional parameters, there is also no first parameter:
[[ -z $1 ]]
test -z "$1"
[ -z "$1" ]
It's just a tiny bit lighter on the reader. Of course it only works when the assumption that the first parameter can't be an empty string is true.
Related
I have a script that receive parameters from user input, but can have also have parameters coming from an environment variable.
e.g. :
export ADZ_DEPLOY_OPT="--from-git --rootDir XXXXXXX"
and in my script, I attempt to do something like :
$*="${ADZ_DEPLOY_OPT} $*"
while [[ $# -gt 1 ]]
do
key="$1"
case $key in
--rootDir)
ADZ_ROOT_DIR="$2"
shift # past argument
;;
--tagVersion)
ADZ_TAG_VERSION="$2"
shift # past argument
;;
--from-git)
ADZ_DEPLOY_FROM_GIT=1
;;
*)
# unknown option
;;
esac
shift # past argument or value
done
Except it doesn't work as it generates an error when I try to update $* to expand it with additional values - and I couldn't succeed to find a way to achieve this behavior.
The idea also is that the env variable will act as 'default' but may be overriden by what the user explicitly provide on the command line. That is with my example, if the user specify another rootDir, I want the user rootDir to be taken into account.
At last, I would like to keep long parameter names (I like being explicit) and therefore the use of getopts doesn't seem like an option.
Any help would be much appreciated !
Thanks in advance.
Replace
$*="${ADZ_DEPLOY_OPT} $*"
with:
set -- ${ADZ_DEPLOY_OPT} "$#"
Notes:
As bash is designed, one cannot assign directly to $*. Assignments are done with the set builtin.
You want to use "$#" not $*. The form $* is subjected to word splitting and the expansion of "$#" is not.
From your sample value for ADZ_DEPLOY_OPT, you do need word splitting for it. If you try to put complicated arguments in ADZ_DEPLOY_OPT, this won't work: you will need to use an array instead.
Do test operators -a and -o short circuit?
I tried if [ 0 -eq 1 -a "" -eq 0 ]; then ... which complained about the syntax of the second conditional. But I can't tell if that's because
-a does not short circuit
or test wants everything properly formatted before it begins and it still short circuits.
The result is leading me to create a nested if when really what I wanted was a situation where the first conditional would guard against executing the second if a particular var had not yet been set...
edit: As for why am I using obsolescent operators, the code has to work everywhere in my environment and I just found a machine where
while [ -L "$file" ] && [ "$n" -lt 10 ] && [ "$m" -eq 0 ]; do
is an infinite loop and changing to the obsolete -a yields good behavior:
while [ -L "$file" -a "$n" -lt 10 -a "$m" -eq 0 ]; do
What should I do? The first expression works on many machines but not this machine which appears to require the second expression instead...
Per the POSIX specification for test:
>4 arguments:
The results are unspecified.
Thus, barring XSI extensions, POSIX says nothing about how this behaves.
Moreover, even on a system with XSI extensions:
expression1 -a expression2: True if both expression1 and expression2 are true; otherwise, false. The -a binary primary is left associative. It has a higher precedence than -o. [Option End]
expression1 -o expression2: True if either expression1 or expression2 is true; otherwise, false. The -o binary primary is left associative. [Option End]
There's no specification with respect to short-circuiting.
If you want short-circuiting behavior -- or POSIX-defined behavior at all -- use && and || to connect multiple, separate test invocations.
Quoting again, from later in the document:
APPLICATION USAGE
The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. (Many expressions using them are ambiguously defined by the grammar depending on the specific expressions being evaluated.) Scripts using these expressions should be converted to the forms given below. Even though many implementations will continue to support these obsolescent forms, scripts should be extremely careful when dealing with user-supplied input that could be confused with these and other primaries and operators. Unless the application developer knows all the cases that produce input to the script, invocations like:
test "$1" -a "$2"
should be written as:
test "$1" && test "$2"
Well, you already know the behaviour, so this question is really about how to interpret those results. But TBH, there aren't many real word scenarios where you'll observe different behaviour.
I created a small test case to check what's going on (at least, on my system, since the other answer suggests it's not standardized):
strace bash -c "if [ 0 -eq 1 -a -e "/nosuchfile" ]; then echo X; fi"
If you check the output you'll see that bash looks for the file, so the answer is:
The operators don't short circuit.
I wrote some bash before reading this popular question. I noticed the way I was doing things doesn't show up in any of the answers so I was curious if there is a danger/bug to my logic that I am not seeing.
if [ ! $1 ] || [ $2 ]; then
echo "Usage: only one var." && exit 1
fi
As you can see, I am testing to see if there is one and only one parameter given on the command line. As far as I have tested, this works. Can someone pass along some more knowledge to a new bash user?
./script one '' oops
[ $2 ] will be false when $2 is the empty string.
And as that other guy points out in a comment, bash will split both strings, opening up a range of issues, including potential security holes.
This is clearer:
if [ $# != 1 ] ; then
echo "Usage: only one var."; exit 1
fi
Things that will break your test:
The first parameter is empty ("")
The second parameter is empty ("")
The first or second parameter has more words ("bla bla")
The parameters contain something that will be interpreted by test (e.g.: -z)
It is simply less clearer than using $#. Also the mere fact that I have to think about all the corner-cases which could potentially break the code makes it inferior.
I know of at least of 4 ways to test conditions in shell scripts.
[ <cond> ];
[[ <cond> ]];
(( <cond> ));
test <cond>;
I would like to have a comprehensive overview of what the differences between these methods are, and also when to use which of the methods.
I've tried searching the web for an summary but didn't find anything decent. It'd be great to have a decent list up somewhere (stack overflow to the rescue!).
Let's describe them here.
First of all, there are basically 3 different test methods
[ EXPRESSION ], which is exactly the same as test EXPRESSION
[[ EXPRESSION ]]
(( EXPRESSION )), which is exactly the same as let "EXPRESSION"
Let's go into the details:
test
This is the grandfather of test commands. Even if your shell does not support it, there's still a /usr/bin/test command on virtually every unix system. So calling test will either run the built-in or the binary as a fallback. Enter $ type test to see which version is used. Likewise for [.
In most basic cases, this should be sufficient to do your testing.
if [ "$a" = test -o "$a" = Test ];
if test "$a" = test -o "$a" = Test;
If you need more power, then there's...
[[]]
This is a bash special. Not every shell needs to support this, and there's no binary fallback. It provides a more powerful comparison engine, notably pattern matching and regular expression matching.
if [[ "$a" == [Tt]es? ]]; # pattern
if [[ "$a" =~ ^[Tt]es.$ ]]; # RE
(())
This is a bash special used for arithmetic expressions, and is true if the result of the calculation is non-zero. Not every shell needs to support this, and there's no binary fallback.
if (( x * (1 + x++) ));
if let "x * (1 + x++)";
Note that you can omit the $ sign when referencing variables within (( ... )) or let.
On the site linked here, if you scroll down to the [ special character, you will see a separate entry for [[, with a link to the discussion of the differences between them. There is also an entry for (( below those. Hope that helps!
Recently I come across below code ...
if [ ! $?target -o $target = "" ]; then
echo "Target is not set"
fi
I am curious to understand logic of above code snippet.
It seems that it is checking, if I am correct, whether "$taget" is set or not or "$target" is empty or not.
I google to find how $?variable works, but I didn't find any relative search results.
So please help me to understand how it works. Thanks in advance.
In some shells, csh for example, $?xyz will evaluate to 1 if xyz is set, otherwise it evaluates to 0.
All that snippet is doing is checking if either $target is not set at all or if it's set to an empty string. In both cases, it assumes it's not set.
Note that this is different from bash for example where $? is the return code from the last executed command. In that shell, you'll simply get the return code followed by the variable name, such as 0target.
I think your script is slightly mis-quoted,
if [ ! "$?target" -o "$target" = "" ]; then
echo "Target is not set"
fi
This will indeed print "Target is not set" when $target is unset, However A much more reliable method for doing this would be:-
if [ "x$foo" = "x" ]; then
echo "foo is not set"
fi
I put "#!sh" at the top, line 1 and ran it on a Mac OS 10.5.Terminal window, which says its bash...
The original script, with [ ! "$?target" -o "$target" = "" ]'
gets
try.sh: line 3: [: too many arguments
The version IanNorton suggests works:
Target is not set
I get the same results with #!bash on line 1, and no shell specification on line 1.
Your mileage may vary...