I am trying to understand a bash script. I couldn't understand a piece of code. I wasn't sure what to google for either. So I'm posting it here. What does it do?
VARIABLE=${VARIABLE:-foo}
It assigns to VARIABLE:
Whatever is in $VARIABLE if it's not unset
foo otherwise
This is sometimes called a "default" parameter:
${parameter-default}, ${parameter:-default}
If parameter not set, use default.
If VARIABLE is not set, or is set to the empty string, then it sets VARIABLE to foo.
Otherwise, it effectively leaves VARIABLE alone, by setting it to its existing value.
The colon makes it treat the empty string as if VARIABLE is not set. If you say ${VARIABLE-foo}, it expands to $VARIABLE even if VARIABLE is set the empty string. This version only expands to foo if VARIABLE is not set at all.
Related
I recently ran into a C program that makes use of an environmental variable as a flag to change the behavior of a certain part of the program:
if (getenv("FOO")) do_this_if_foo();
You'd then request the program by prepending the environment variable, but without actually setting it to anything:
FOO= mycommand myargs
Note that the intention of this was to trigger the flag - if you didn't want the added operation, you just wouldn't include the FOO=. However, I've never seen an environment variable set like this before. Every example I can find of prepended variables sets a value, FOO=bar mycommand myargs, rather than leaving it empty like that.
What exactly is happening here, that allows this flag to work without being set? And are there potential issues with implementing environmental variables like this?
The bash manual says:
A variable may be assigned to by a statement of the form
name=[value]
If value is not given, the variable is assigned the null string.
Note that "null" (in the sense of e.g. JavaScript null) is not a thing in the shell. When the bash manual says "null string", it means an empty string (i.e. a string whose length is zero).
Also:
When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.
[...]
If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.
So all FOO= mycommand does is set the environment variable FOO to the empty string while executing mycommand. This satisfies if (getenv("FOO")) because it only checks for the presence of the variable, not whether it has a (non-empty) value.
Of course, any other value would work as well: FOO=1 mycommand, FOO=asdf mycommand, etc.
FOO= is just setting the variable to null (to be precise it's setting the variable to a zero-byte string, which thus returns a pointer to a NUL terminator - thanks #CharlesDuffy). Given the code you posted it could be FOO='bananas'and produce the same behavior. It's very odd to write code that way though. The common reason to set a variable on the command line is to pass a value for that variable into the script, e.g. to set debugging or logging level flags is extremely common, e.g. (pseudocode):
debug=1 logLevel=3 myscript
myscript() {
if (debug == 1) {
if (loglevel > 0) {
printf "Entering myscript()\n" >> log
if (logLevel > 1) {
printf "Arguments: %s\n" "$*" >> log
}
}
}
do_stuff()
}
Having just a "variable exists" test is a bit harder to work with because then you have to specifically unset the variable to clear the flag instead of just setting FOO=1 when you want to do something and otherwise your script doesn't care when FOO is null or 0 or unset or anything else.
I'm reading this:
You can delete a variable with the command unset varname. Normally this is not useful, since all variables that don't exist are assumed to be null, i.e., equal to empty string "". But if you use the option nounset which causes the shell to indicate an error when it encounters an undefined variable, then you may be interested in unset.
My first question is: I cannot see why the use of unset be not useful; if I want to put my variable to null I can use it (or set variable="" or variable=). On the other hand, if I have a variable that doesn't exist, I don't know why I should have to use it..
My second question is: Why may I be interested in unset in that case?
There is a relevant difference between unset and empty variables.
When you can't tell in front which variables will be used, you can process the output of set (examples: https://stackoverflow.com/a/43419722/3220113 and https://stackoverflow.com/a/28104421/3220113 ).
You might have a situaton where you have sourced a read-only config file, but you do not want all lines set in your environment. In that case you might want to unset the settings you do not need.
When you write some utility that uses some variables, you do not want to leave garbage in the environment. Next to using local variables using unset is another possibility.
I think I have found the answer to my question.
1) If you need to remove the definition and the content of a variable you can use unset command. However, unless you turn on the nounset set option, Korn Shell will allow using variables which don't exist, and it will default the content of such a variable as an empty string. That's why you normally don't use unset: because you normally leave the nounset option off and test variables via conditional logic. Hence in these cases, i.e. the inhibition of the use of a variable, it is not useful. (Obviously, it remains useful for deleting variables - as noted by #Walter A, i.e. "" is not unset, the complete removal of the variable.)
2) That said, it follows that if you use the nounset, unset command makes sense. Indeed, if you unset a variable, the shell will disallow using it.
In general, this syntax is used to guarantee a value, potentially a default argument.
(from the Bash reference manual)
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted.
Otherwise, the value of parameter is substituted.
What would be the purpose of defaulting a variable to empty if the substitution is only chosen when the variable is empty anyway?
For reference, I'm looking at /lib/lsb/init-functions.
"Null" means the variable has a value, and this value is an empty string. The shell knows the variable exists.
"Unset" means the variable has not been defined : it does not exist as far as the shell is concerned.
In its usual mode, the shell will expand null and unset variable to an empty string. But there is a mode (set -u) that allows the shell to throw a runtime error if a variable is expanded when it is unset. It is good practice to enable this mode, because it is very easy to simply mis-type a variable name and get difficult to debug errors.
It can actually be useful from a computing perspective to differentiate between unset and empty variables, you can assign separate semantics to each case. For instance, say you have a function that may receive an argument. You may want to use a (non-null) default value if the parameter is unset, or any value passed to the function (including an empty string) if the parameter is set. You would do something like :
my_function()
{
echo "${1-DEFAULT_VALUE}"
}
Then, the two commands below would provide different outputs:
my_function # Echoes DEFAULT_VALUE
my_function "" # Echoes an empty line
There is also a type of expansion that does not differentiate between null and not set :
"${VAR:-DEFAULT_VALUE}"
They are both useful depending on what you need.
The way to test if a variable is set or not (without running the risk of a runtime error) is the following type of expansion :
"${VAR+VALUE}"
This will expand to an empty string if VAR is unset, or to VALUE if it is set (empty or with a value). Very useful when you need it.
Generally, it is helpful to:
Declare variables explicitely
set -u to prevent silent expansion failure
Explicitly handle unset variables through the appropriate expansion
This will make your scripts more reliable, and easier to debug.
I want to insert the value of an environment variable in a string or a default value if the corresponding variable is not initialized.
Example:
if [ -z $MY_VAR ];
then
MY_VAR="default"
fi
echo "my variable contains $MY_VAR"
I'm however using a lot of variables in my strings and the tests are cluttering my script.
Is there a way to make a ternary expression in my string?
Example of what I want to achieve (it doesn't work):
echo "my variable contains ${-z $MY_VAR ? $MY_VAR : 'default'}"
To actually set the value of the variable, rather than just expanding to a default if it has no value, you can use this idiom:
: ${MY_VAR:=default}
which is equivalent to your original if statement. The expansion has the side effect of actually changing the value of MY_VAR if it is unset or empty. The : is just the do-nothing command which provides a context where we can use the parameter expansion, which is treated as an argument that : ignores.
See Bash Default Values
→ echo "my variable contains ${MY_VAR:-default}"
my variable contains default
I noticed that in shell script when we declare a variable, the preceding dollar sign is not needed, although when we want to access this variable later we should add a dollar sign in front of this variable name.
just like:
#!/bin/sh
VAR_1=Hello
VAR_2=Unix
echo "$VAR_1 $VAR_2"
This is different from other languages, like Perl we will always have the preceding dollar sign with the variable name, I just want to know any good reason for shell script to do it in this way, or it's just a convention...?
Shell is a different language than Perl is a different language than C++ is a different language than Python. You can add "with different rules" to each of the languages.
In shell an identifier like VAR_1 names a variable, the dollar sign is used to invoke expansion. $var is replaced with var's content; ${var:-foo} is replaced with var's content if it is set and with the word foo if the variable isn't set. Expansion works on non-variables as well, e.g. you can chain expansion like ${${var##*/}%.*} should leave only a file base name if var contains a file name with full path and extension.
In Perl the sigil in front of the variable tells Perl how to interpret the identifier: $var is a scalar, #var an array, %var a hash etc.
In Ruby the sigil in front of the varible tells Ruby its scope: var is a local variable, $var is a global one, #var is an instance variable of an object and ##var is a class variable.
In C++ we don't have sigils in front of variable names.
Etc.
In the shell, the $ sign is not part of the variable name. It just tells the shell to replace the following word with the contents of the variable with the same name, i.e. $foo means "insert the contents of the variable foo here".
This is not used when assigning to the variable because there you explicitly don't want to insert the old contents; you want to use the variable itself (in some ways this is similar to dereferencing pointers).
It's basically a syntactical convention.
DOS/.bat file syntax works the same way.
1) to create a variable, no metacharacter.
2) to "dereference" the contents of the variable, use the metacharacter.
DOS:
set VAR=123
echo %VAR%