BASH case not catching empty string
BASH case not catching empty string
I want to detect if either no arguments or an invalid argument is passed and print a help message. A separate check for an empty argument is possible, but not so elegant.
My bash script looks like this:
COMMAND="$1"
shift
case "$COMMAND" in
loop)
loop_
;;
...
*)
echo $"Usage: $0 {loop|...}"
exit 1
esac
When no arguments are passed, nothing executes; if I pass "" then the proper case is triggered. If I use $1
directly instead of using the temporary variable, then it works as expected.
$1
I've even tried adding a specific case for "")
but to no avail.
"")
@PesaThe I need the default case anyway, to catch an invalid command. I would like to include the "no command" case in the switch and not as a separate check.
– afuna
10 hours ago
...anyhow,
*)
does match the empty case. If you want to claim that it doesn't, you'll need to provide a Minimal, Complete, and Verifiable example letting someone else see the issue for themselves. See ideone.com/rGIvg4 showing your code -- modified only to add the missing ;;
-- properly emitting a usage error when no command is given.– Charles Duffy
29 mins ago
*)
;;
3 Answers
3
The only way your case
statement isn't going to match with no $1
given is if it isn't entered in the first place.
case
$1
Consider the following:
#!/usr/bin/env bash
set -e
command=$1
shift
case $command in
*) echo "Default case was entered";;
esac
This emits no output when $1
is unset -- but not because anything wrong with the case
statement.
$1
case
Rather, the issue is that shift
exits with a nonzero exit status when there's nothing available to shift, and the set -e
causes the script as a whole to exit on that failure.
shift
set -e
set -e
#!/bin/bash -e
See BashFAQ #105 for an extended discussion -- or the exercises included therein if in a hurry. set -e
is wildly incompatible between different "POSIX-compliant" shells, and thus makes behavior hard to predict. Manual error handling may not be fun, but it's much more reliable.
set -e
This gives you a terse way to have your usage message in one place, and re-use it where necessary (for example, if you don't have a $1
to shift
):
$1
shift
#!/usr/bin/env bash
usage() { echo "Usage: $0 {loop|...}" >&2; exit 1; }
command=$1
shift || usage
case $command in
*) usage ;;
esac
Because of the || usage
, the exit status of shift
is considered "checked", so even if you do run your script with set -e
, it will no longer constitute a fatal error.
|| usage
shift
set -e
shift
Similarly:
shift ||:
...will run shift
, but then fall back to running :
(a synonym for true
, which historically/conventionally implies placeholder use) should shift
fail, similarly preventing set -e
from triggering.
shift
:
true
shift
set -e
POSIX specifies that the shell (and other tools to which the standards applies) have their behavior modified only by environment variables with all-caps names:
Environment variable names used by the utilities in the Shell and Utilities volume of POSIX.1-2017 consist solely of uppercase letters, digits, and the ( '_' ) from the characters defined in Portable Character Set and do not begin with a digit. Other characters may be permitted by an implementation; applications shall tolerate the presence of such names. Uppercase and lowercase letters shall retain their unique identities and shall not be folded together. The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities.
This applies even to regular, non-export
ed shell variables because specifying a shell variable with the same name as an environment variable overwrites the latter.
export
BASH_COMMAND
, for example, has a distinct meaning in bash -- and thus can be set to a non-empty value at the front of your script. There's nothing stopping COMMAND
from similarly being meaningful to, and already used by, a POSIX-compliant shell interpreter.
BASH_COMMAND
COMMAND
If you want to avoid side effects from cases where your shell has set a built-in variable with a name your script uses, or where your script accidentally overwrites a variable meaningful to the shell, stick to lowercase or mixed-case names when writing scripts for POSIX-compliant shells.
You can simply use $#
. It represents the number of given arguments:
$#
if [ $# = 0 ]
then
echo "help ..."
fi
I would like to retain the
*)
case for when an invalid command is given, and I don't wan't to repeat the help message... [I could use a function etc..]. I hope there's a cleaner way.– afuna
10 hours ago
*)
in the line: echo $"Usage: $0 {loop|...}"
what's the first $
for?
echo $"Usage: $0 {loop|...}"
$
If you don't want to repeat the message, just put it in a function and check for an empty string before the case statement.
#! /bin/bash
die()
{
"Usage: $0 {loop|...}"
exit 1
}
COMMAND="$1"
[ -z $COMMAND ] && die
shift
case "$COMMAND" in
loop)
loop_
;;
*)
die
exit 1
;;
esac
$"..."
looks up the string in the active translation table; it's a feature for locale/multilingual support in scripts.– Charles Duffy
34 mins ago
$"..."
And your code tries to run
Usage:
as a command, not echo it, so it throws an error.– Charles Duffy
34 mins ago
Usage:
Also,
[ -z $foo ]
is broken. If $foo
is empty, it becomes [ -z ]
, which is equivalent to [ -n '-z' ]
. If foo='1 -o 1 = 1'
, it becomes [ -z 1 -o 1 = 1 ]
, the string isn't empty, but you still get a true result. Always use quotes: [ -z "$foo" ]
– Charles Duffy
30 mins ago
[ -z $foo ]
$foo
[ -z ]
[ -n '-z' ]
foo='1 -o 1 = 1'
[ -z 1 -o 1 = 1 ]
[ -z "$foo" ]
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Possible duplicate of Check existence of input argument in a Bash shell script
– PesaThe
11 hours ago