What does "plus colon" ("+:") mean in shell script expressions? - bash

What does this mean?
if ${ac_cv_lib_lept_pixCreate+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
Looks like ac_cv_lib_lept_pixCreate is some variable, so what does +: mean?
Where to learn complete syntax of curly bracket expressions? What is the name of this syntax?

In the “plus colon” ${...+:} expression, only the + has special meaning in the shell. The colon is just a string value in this case, so we could write that snippet as ${...+":"}.
For convenience, let's pretend the variable is called var, and consider the expression:
if ${var+:} false; then ...
If the shell variable $var exists, the entire expression is replaced with :, if not, it returns an empty string.
Therefore the entire expression ${var+:} false becomes either : false (returning true) or false (returning false).
This comes down to a test for existence, which can be true even if the variable has no value assigned.
It is very cryptic, but as it happens, is one of the few tests for the existence of a variable that actually works in most, if not all, shells of Bourne descent.
Possible equivalents: (substitute any variable name here for var)
if [[ ${var+"is_set"} == is_set ]]; then ...
Or, probably more portable:
case ${var+"IS_SET"} in IS_SET) ...;; esac

Shell Parameter Expansion documentation for bash is here. No mention of +:, though it does mention :+:
${parameter:+word}
If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.

To illustrate what has already been said:
Unset variable (note the blank lines as a result of some echo commands):
$ unset foo
$ echo ${foo}
$ echo ${foo:+:}
$ echo ${foo+:}
Null variable:
$ foo=""
$ echo ${foo}
$ echo ${foo:+:}
$ echo ${foo+:}
:
Non-null variable:
$ foo="bar"
$ echo ${foo}
bar
$ echo ${foo:+:}
:
$ echo ${foo+:}
:

Simple examples will prove
I check for presence of a parameter TEST, if present echo "Yes" else I echo "No"
openvas:~$ ${TEST+:} false && echo "yes" || echo "no"
no
openvas:~$ TEST=1
openvas:~$ ${TEST+:} false && echo "yes" || echo "no"
yes
openvas:~$
If you see, the parameter TEST is evaluated and it is actually unset, so it returns false and exit the path and goes to the OR
Once I set the same, and test again, it flows to the echo part (continued &&) since it returns true
Refer: this and that

Related

How can I override a bash variable at the time of executing it in console? [duplicate]

I have a whole bunch of tests on variables in a bash (3.00) shell script where if the variable is not set, then it assigns a default, e.g.:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
I seem to recall there's some syntax to doing this in one line, something resembling a ternary operator, e.g.:
FOO=${ ${VARIABLE} : 'default' }
(though I know that won't work...)
Am I crazy, or does something like that exist?
Very close to what you posted, actually. You can use something called Bash parameter expansion to accomplish this.
To get the assigned value, or default if it's missing:
FOO="${VARIABLE:-default}" # If variable not set or null, use default.
# If VARIABLE was unset or null, it still is after this (no assignment done).
Or to assign default to VARIABLE at the same time:
FOO="${VARIABLE:=default}" # If variable not set or null, set it to default.
For command line arguments:
VARIABLE="${1:-$DEFAULTVALUE}"
which assigns to VARIABLE the value of the 1st argument passed to the script or the value of DEFAULTVALUE if no such argument was passed. Quoting prevents globbing and word splitting.
If the variable is same, then
: "${VARIABLE:=DEFAULT_VALUE}"
assigns DEFAULT_VALUE to VARIABLE if not defined.
The colon builtin (:) ensures the variable result is not executed
The double quotes (") prevent globbing and word splitting.
Also see Section 3.5.3, Shell Parameter Expansion, in the Bash manual.
To answer your question and on all variable substitutions
echo "${var}"
echo "Substitute the value of var."
echo "${var:-word}"
echo "If var is null or unset, word is substituted for var. The value of var does not change."
echo "${var:=word}"
echo "If var is null or unset, var is set to the value of word."
echo "${var:?message}"
echo "If var is null or unset, message is printed to standard error. This checks that variables are set correctly."
echo "${var:+word}"
echo "If var is set, word is substituted for var. The value of var does not change."
You can escape the whole expression by putting a \ between the dollar sign and the rest of the expression.
echo "$\{var}"
Even you can use like default value the value of another variable
having a file defvalue.sh
#!/bin/bash
variable1=$1
variable2=${2:-$variable1}
echo $variable1
echo $variable2
run ./defvalue.sh first-value second-value output
first-value
second-value
and run ./defvalue.sh first-value output
first-value
first-value
see here under 3.5.3(shell parameter expansion)
so in your case
${VARIABLE:-default}
FWIW, you can provide an error message like so:
USERNAME=${1:?"Specify a username"}
This displays a message like this and exits with code 1:
./myscript.sh
./myscript.sh: line 2: 1: Specify a username
A more complete example of everything:
#!/bin/bash
ACTION=${1:?"Specify 'action' as argv[1]"}
DIRNAME=${2:-$PWD}
OUTPUT_DIR=${3:-${HOMEDIR:-"/tmp"}}
echo "$ACTION"
echo "$DIRNAME"
echo "$OUTPUT_DIR"
Output:
$ ./script.sh foo
foo
/path/to/pwd
/tmp
$ export HOMEDIR=/home/myuser
$ ./script.sh foo
foo
/path/to/pwd
/home/myuser
$ACTION takes the value of the first argument, and exits if empty
$DIRNAME is the 2nd argument, and defaults to the current directory
$OUTPUT_DIR is the 3rd argument, or $HOMEDIR (if defined), else, /tmp. This works on OS X, but I'm not positive that it's portable.
Then there's the way of expressing your 'if' construct more tersely:
FOO='default'
[ -n "${VARIABLE}" ] && FOO=${VARIABLE}
It is possible to chain default values like so:
DOCKER_LABEL=${GIT_TAG:-${GIT_COMMIT_AND_DATE:-latest}}
i.e. if $GIT_TAG doesnt exist, take $GIT_COMMIT_AND_DATE - if this doesnt exist, take "latest"
Here is an example
#!/bin/bash
default='default_value'
value=${1:-$default}
echo "value: [$value]"
save this as script.sh and make it executable.
run it without params
./script.sh
> value: [default_value]
run it with param
./script.sh my_value
> value: [my_value]
If you want 1 liner for your if-then-else, then we can consider rewriting:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
with semicolons:
if [ -z ${VARIABLE} ]; then FOO=`default`; else FOO=${VARIABLE}; fi
Alternatively, you can drop the if-then-else-fi keywords if you use boolean operators such as:
[ -z "${VARIABLE}" ] && FOO='default' || FOO=${VARIABLE}
Generalizing, the boolean operator pattern is:
conditional && then_command || else_command

Is unset parameter in [[ compound command expected as empty string in bash?

I am learning [[ compound command, that is, new if sentence.
Some sites including bashFAQ/31 on What is the difference between test, [ and [[ ?, say that it does not need to quote parameter in it.
My question is what exactly happened with unquoted unset parameter.
I tried following code,
unset UNDEF_VAR
[[ ${UNDEF_VAR} = "" ]]
echo "result is $?"
and the output was
result is 0
So, I expect that unset variable, ${UNDEF_VAR}, between [[ ]] is translated as empty string on the contrary to the case of old if, "[".
But unfortunately, I could not find the explicit explanation in sites above and bash manual. Some sites just says it is OK without the reason.
Someone who knows the mechanism, please let me know. If I missed the explanation in sites/manual carelessly, please forgive me for using your time. Thank you very much.
As far as I can find, the only reference to the expansion of an unset parameter is this sentence from the POSIX spec:
The value, if any, of parameter shall be substituted.
A consequence of this is that "$foo" is an empty string because no value is placed between the quotes.
The bash man page has this to say about word-splitting:
Explicit null arguments ("" or '') are retained. Unquoted implicit
null arguments, resulting from the expansion of parameters that have no
values, are removed. If a parameter with no value is expanded within
double quotes, a null argument results and is retained.
Inside [[ ... ]], a parameter expansion does not undergo word-splitting, so it is not clear exactly where "no value to substitute" becomes "null value". I assume that parameter expansions are treated as being implicitly quoted, whether or not they actually are.
The [[ version behaves much more like you would expect it (at least as I would). It's independent if the variable is unset or empty.
$ unset foo
$ [[ $foo == "" ]] && echo true || echo false
true
$ foo=""
$ [[ $foo == "" ]] && echo true || echo false
true
With [ you have to be careful. You need to quote it, if there's any chance that it might be empty or unset:
$ [ $foo = "" ] && echo true || echo false
bash: [: =: unary operator expected
false
$ [ "$foo" = "" ] && echo true || echo false
true
Also note that when you use [[, you should compare with ==, and if you use [, compare with =. AFAIR zsh causes trouble, if you mix them.
And of course, as #fedorqui noted, comparing empty strings should be done with -z.

In a function Bash: how to check if an argument is a set variable?

I want to implement a bash function which test is the 1st argument is actually a variable, defined somewhere.
For instance, in my .bashrc :
customPrompt='yes';
syntaxOn='no';
[...]
function my_func {
[...]
# I want to test if the string $1 is the name of a variable defined up above
# so something like:
if [[ $$1 == 'yes' ]];then
echo "$1 is set to yes";
else
echo "$1 is not set or != to yes";
fi
# but of course $$1 doesn't work
}
output needed :
$ my_func customPrompt
> customPrompt is set to yes
$ my_func syntaxOn
> syntaxOn is set but != to yes
$ my_func foobar
> foobar is not set
I tried a lot of test, like -v "$1", -z "$1", -n "$1", but all of them test $1 as a string not as a variable.
(please correct me if I make not myself clear enought)
In the bash you can use the indirect variable subtituion.
t1=some
t2=yes
fufu() {
case "${!1}" in
yes) echo "$1: set to yes. Value: ${!1}";;
'') echo "$1: not set. Value: ${!1:-UNDEF}";;
*) echo "$1: set to something other than yes. Value: ${!1}";;
esac
}
fufu t1
fufu t2
fufu t3
prints
t1: set to something other than yes. Value: some
t2: set to yes. Value: yes
t3: not set. Value: UNDEF
The ${!variablename} in bash mean indirect variable expansion. Described in the e.g. https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
Whrere:
The basic form of parameter expansion is ${parameter}. The value of
parameter is substituted. The braces are required when parameter is a
positional parameter with more than one digit, or when parameter is
followed by a character that is not to be interpreted as part of its
name.
If the first character of parameter is an exclamation point (!), a
level of variable indirection is introduced. Bash uses the value of
the variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
This is known as indirect expansion. The exceptions to this are the
expansions of ${!prefix } and ${!name[#]} described below. The
exclamation point must immediately follow the left brace in order to
introduce indirection.
Also, check this: https://stackoverflow.com/a/16131829/632407 how to modify in a function a value of the variable passed indirectly.
You can check variable set or not by simply like
if [[ $var ]]
then
echo "Sorry First set variable"
else
echo $var
fi
You can do something like this for your script
customPrompt='yes';
syntaxOn='no';
function my_func
{
if [[ ${!1} ]];then
echo "$1 is set to ${!1}";
else
echo "$1 is not set";
fi
}
my_func customPrompt
my_func syntaxOn
my_func foobar
Output:
customPrompt is set to yes
syntaxOn is set to no
foobar is not set
You can customize the function as per you requirement by simply making some comparison conditions.
For more details you can check this answer
If you really want to check if your variable is set or unset (not just empty), use this format:
function my_func {
if [[ -z ${!1+.} ]]; then
echo "$1 is not set."
elif [[ ${!1} == yes ]]; then
echo "$1 is set to yes"
else
echo "$1 is set to \"${!1}\"."
fi
}
You're going to have problems...
The Bash shell is a very wily creature. Before you execute anything, Bash comes in and interpolates your command. Your command or shell script never sees whether or not you have a variable as a parameter.
$ set -x
set -x
$ foo=bar
+ foo=bar
$ echo "$foo"
+ echo bar
bar
$ set +x
The set -x turns on debugging mode in the shell. It shows you what a command actually executes. For example, I set foo=bar and then do echo $foo. My echo command doesn't see $foo. Instead, before echo executes, it interpolates $foo with bar. All echo sees at this point is that it's suppose to take bar as its argument (not $foo).
This is awesomely powerful. It means that your program doesn't have to sit there and interpret the command line. If you typed echo *.txt, echo doesn't have to expand *.txt because the shell has already done the dirty work.
For example, here's a test shell script:
#! /bin/sh
if [[ $1 = "*" ]]
then
echo "The first argument was '*'"
else
"I was passed in $# parameters"
fi
Now, I'll run my shell script:
$ test.sh *
I was passed in 24 parameters
What? Wasn't the first parameter of my script a *? No. The shell grabbed * and expanded it to be all of the files and directories in my directory. My shell script never saw the *. However, I can do this:
$ test.sh '*'
The first argument was '*'
The single quotes tell the shell not to interpolate anything. (Double quotes prevent globbing, but still allow for environment variable expansion).
This if I wanted to see if my first parameter is a variable, I have to pass it in single quotes:
$ test.sh '$foo'
And, I can do this as a test:
if [[ $1 != ${1#$} ]]
then
echo "The first parameter is the variable '$1'"
fi
The ${1#$} looks a bit strange, but it's just ${var#pattern}. This removes pattern from the left most side of $var. I am taking $1 and removing the $ if it exists. This gets expanded in the shell as:
if [[ $foo != foo ]]
which is true.
So, several things:
First, you've got to stop the shell from interpolating your variable. That means you have to use single quotes around the name.
You have to use pattern matching to verify that the first parameter starts with a $.
Once you do that, you should be able to use your variable with ${$1} in your script.

Assigning default values to shell variables with a single command in bash

I have a whole bunch of tests on variables in a bash (3.00) shell script where if the variable is not set, then it assigns a default, e.g.:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
I seem to recall there's some syntax to doing this in one line, something resembling a ternary operator, e.g.:
FOO=${ ${VARIABLE} : 'default' }
(though I know that won't work...)
Am I crazy, or does something like that exist?
Very close to what you posted, actually. You can use something called Bash parameter expansion to accomplish this.
To get the assigned value, or default if it's missing:
FOO="${VARIABLE:-default}" # If variable not set or null, use default.
# If VARIABLE was unset or null, it still is after this (no assignment done).
Or to assign default to VARIABLE at the same time:
FOO="${VARIABLE:=default}" # If variable not set or null, set it to default.
For command line arguments:
VARIABLE="${1:-$DEFAULTVALUE}"
which assigns to VARIABLE the value of the 1st argument passed to the script or the value of DEFAULTVALUE if no such argument was passed. Quoting prevents globbing and word splitting.
If the variable is same, then
: "${VARIABLE:=DEFAULT_VALUE}"
assigns DEFAULT_VALUE to VARIABLE if not defined.
The colon builtin (:) ensures the variable result is not executed
The double quotes (") prevent globbing and word splitting.
Also see Section 3.5.3, Shell Parameter Expansion, in the Bash manual.
To answer your question and on all variable substitutions
echo "${var}"
echo "Substitute the value of var."
echo "${var:-word}"
echo "If var is null or unset, word is substituted for var. The value of var does not change."
echo "${var:=word}"
echo "If var is null or unset, var is set to the value of word."
echo "${var:?message}"
echo "If var is null or unset, message is printed to standard error. This checks that variables are set correctly."
echo "${var:+word}"
echo "If var is set, word is substituted for var. The value of var does not change."
You can escape the whole expression by putting a \ between the dollar sign and the rest of the expression.
echo "$\{var}"
Even you can use like default value the value of another variable
having a file defvalue.sh
#!/bin/bash
variable1=$1
variable2=${2:-$variable1}
echo $variable1
echo $variable2
run ./defvalue.sh first-value second-value output
first-value
second-value
and run ./defvalue.sh first-value output
first-value
first-value
see here under 3.5.3(shell parameter expansion)
so in your case
${VARIABLE:-default}
FWIW, you can provide an error message like so:
USERNAME=${1:?"Specify a username"}
This displays a message like this and exits with code 1:
./myscript.sh
./myscript.sh: line 2: 1: Specify a username
A more complete example of everything:
#!/bin/bash
ACTION=${1:?"Specify 'action' as argv[1]"}
DIRNAME=${2:-$PWD}
OUTPUT_DIR=${3:-${HOMEDIR:-"/tmp"}}
echo "$ACTION"
echo "$DIRNAME"
echo "$OUTPUT_DIR"
Output:
$ ./script.sh foo
foo
/path/to/pwd
/tmp
$ export HOMEDIR=/home/myuser
$ ./script.sh foo
foo
/path/to/pwd
/home/myuser
$ACTION takes the value of the first argument, and exits if empty
$DIRNAME is the 2nd argument, and defaults to the current directory
$OUTPUT_DIR is the 3rd argument, or $HOMEDIR (if defined), else, /tmp. This works on OS X, but I'm not positive that it's portable.
Then there's the way of expressing your 'if' construct more tersely:
FOO='default'
[ -n "${VARIABLE}" ] && FOO=${VARIABLE}
It is possible to chain default values like so:
DOCKER_LABEL=${GIT_TAG:-${GIT_COMMIT_AND_DATE:-latest}}
i.e. if $GIT_TAG doesnt exist, take $GIT_COMMIT_AND_DATE - if this doesnt exist, take "latest"
Here is an example
#!/bin/bash
default='default_value'
value=${1:-$default}
echo "value: [$value]"
save this as script.sh and make it executable.
run it without params
./script.sh
> value: [default_value]
run it with param
./script.sh my_value
> value: [my_value]
If you want 1 liner for your if-then-else, then we can consider rewriting:
if [ -z "${VARIABLE}" ]; then
FOO='default'
else
FOO=${VARIABLE}
fi
with semicolons:
if [ -z ${VARIABLE} ]; then FOO=`default`; else FOO=${VARIABLE}; fi
Alternatively, you can drop the if-then-else-fi keywords if you use boolean operators such as:
[ -z "${VARIABLE}" ] && FOO='default' || FOO=${VARIABLE}
Generalizing, the boolean operator pattern is:
conditional && then_command || else_command

Test for a Bash variable being unset, using a function

A simple Bash variable test goes:
${varName:? "${varName} is not defined"}
I'd like to reuse this, by putting it in a function. How can I do it?
The following fails
#
# Test a variable exists
tvar(){
val=${1:? "${1} must be defined, preferably in $basedir"}
if [ -z ${val} ]
then
echo Zero length value
else
echo ${1} exists, value ${1}
fi
}
I.e., I need to exit if the test fails.
Thanks to lhunath's answer, I was led to a part of the Bash man page that I've overlooked hundreds of times:
When not performing substring expansion, bash tests for a parameter that is unset or null; omitting the colon results in a test only for a parameter that is unset.
This prompted me to create the following truth table:
Unset
Set, but null
Set and not null
Meaning
${var-_}
T
F
T
Not null or not set
${var:-_}
T
T
T
Always true, use for subst.
$var
F
F
T
'var' is set and not null
${!var[#]}
F
T
T
'var' is set
This table introduces the specification in the last row. The Bash man page says "If name is not an array, expands to 0 if name is set and null otherwise." For purposes of this truth table, it behaves the same even if it's an array.
You're looking for indirection.
assertNotEmpty() {
: "${!1:? "$1 is empty, aborting."}"
}
That causes the script to abort with an error message if you do something like this:
$ foo=""
$ assertNotEmpty foo
bash: !1: foo is empty, aborting.
If you just want to test whether foo is empty, instead of aborting the script, use this instead of a function:
[[ $foo ]]
For example:
until read -p "What is your name? " name && [[ $name ]]; do
echo "You didn't enter your name. Please, try again." >&2
done
Also, note that there is a very important difference between an empty and an unset parameter. You should take care not to confuse these terms! An empty parameter is one that is set, but just set to an empty string. An unset parameter is one that doesn't exist at all.
The previous examples all test for empty parameters. If you want to test for unset parameters and consider all set parameters OK, whether they're empty or not, use this:
[[ ! $foo && ${foo-_} ]]
Use it in a function like this:
assertIsSet() {
[[ ! ${!1} && ${!1-_} ]] && {
echo "$1 is not set, aborting." >&2
exit 1
}
}
Which only aborts the script when the parameter name you pass denotes a parameter that isn't set:
$ ( foo="blah"; assertIsSet foo; echo "Still running." )
Still running.
$ ( foo=""; assertIsSet foo; echo "Still running." )
Still running.
$ ( unset foo; assertIsSet foo; echo "Still running." )
foo is not set, aborting.
You want to use [ -z ${parameter+word} ]
Some part of man bash:
Parameter Expansion
...
In each of the cases below, word is subject to tilde expansion, parameter expansion, command substitution, and
arithmetic expansion. When not performing substring expansion, bash tests for a parameter that is unset or null;
omitting the colon results in a test only for a parameter that is unset.
...
${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of
word is substituted.
...
in other words:
${parameter+word}
Use Alternate Value. If parameter is unset, nothing is substituted, otherwise the expansion of
word is substituted.
some examples:
$ set | grep FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
it is unset
$ declare FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ FOOBAR=
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ FOOBAR=1
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
$ unset FOOBAR
$ if [ -z "${FOOBAR+something}" ]; then echo "it is unset"; fi
it is unset
$
This function tests for variables that are currently set. The variable may even be an array. Note that in Bash: 0 == TRUE, 1 == FALSE.
function var.defined {
eval '[[ ${!'$1'[#]} ]]'
}
# Typical usage of var.defined {}
declare you="Your Name Here" ref='you';
read -p "What's your name: " you;
if var.defined you; then # Simple demo using literal text
echo "BASH recognizes $you";
echo "BASH also knows a reference to $ref as ${!ref}, by indirection.";
fi
unset you # Have just been killed by a master :D
if ! var.defined $ref; then # Standard demo using an expanded literal value
echo "BASH doesn't know $ref any longer";
fi
read -s -N 1 -p "Press any key to continue...";
echo "";
So to be clear here, the function tests literal text. Every time a command is called in Bash, variables are generally 'swapped-out' or 'substituted' with the underlying value unless:
$varRef ($) is escaped: $varRef
$varRef is single quoted '$varRef'
I.e., I need to exit if the test fails.
The code:
${varName:? "${varName} is not defined"}
will return a nonzero exit code when there is not a variable named "varName". The exit code of the last command is saved in $?.
About your code:
val=${1:? "${1} must be defined, preferably in $basedir"}
Maybe it is not doing what you need. In the case that $1 is not defined, the "${1}" will be substituted with nothing. Probably you want use the single quotes that literally writes ${1} without substitution.
val=${1:? '${1} must be defined, preferably in $basedir'
I am unsure if this is exactly what you want, but a handy trick I use when writing a new and complex script is to use "set -o":
set -o # Will make the script bomb out when it finds an unset variable
For example,
$ grep '$1' chex.sh
case "$1" in
$ ./chex.sh
./chex.sh: line 111: $1: unbound variable
$ ./chex.sh foo
incorrect/no options passed.. exiting
if set | grep -q '^VARIABLE='
then
echo VARIABLE is set
fi

Resources