Why does
${username=`whoami`}
throw an error, whereas
: ${username=`whoami`}
performs an assignment without any ill effects?
I understand : is a placeholder. What is its use in this command? Is it the equivalent of running : 'whoami'?
For reference, the former usage was previously referred to as #3, and the new one as #4.
${parameter=value}
does two things: It has the side effect of assigning value to parameter if parameter is not already set, and the direct effect of expanding to the value of parameter when complete.
The error is the result of that direct effect: When you run
${user=`whoami`}
...on its own line, then that expands to, and tries to run, the output of whoami as a command. Let's say that the user variable is not previously assigned to, and the output of whoami is james; it would then try to run the command james, which would throw an error.
By contrast, running
: ${user=`whoami`}
...first performs the side effect (of doing an assignment to user if user is not already set), and then runs:
: james
...which has no effect, so only the side effect (of the assignment) is performed.
In #3, according to the bash manual pages, you are trying to execute the whoami command output, i.e, if whoami command output is "peter", #3 means that the "peter" command is invoked. Moreover, "username" variable is assigned the value "peter"
The bash manual describes ${parameter:=word} as follows:
Assign Default Values. If parameter is unset or null, the expansion of word is assigned to parameter. The value of parameter is then substituted. Positional parameters and special parameters may not be assigned to in this way.
Likewise, for the : command --
No effect; the command does nothing beyond expanding arguments and performing any specified redirections. A zero exit code is returned.
Related
I am learning shell scripting and came across this line
: ${CONTAINER_CLI:="docker"}
can someone please explain me what this line do? what is the meaning of : here?
man bash command said:
${parameter:=word}
Assign Default Values.
If parameter is unset or null, the expansion of word is
assigned to parameter.
The value of parameter is then substituted.
Positional parameters and special parameters
may not be assigned to in this way.
So, the variable CONTAINER_CLI is assigned with the value docker if CONTAINER_CLI does not exists or empty.
But... if you simply write :
${CONTAINER_CLI:="docker"}
You obtain an error if the result is not a command.
You just want make a assignation.
Put simply a : before.
It's like:
CONTAINER_CLI=${CONTAINER_CLI:="docker"}
I wrote the following bash script:
${MY_FLAG:=true}
${LOG_FILE:="something.log"}
I am trying to assign true to MY_FLAG and the string "something.log" to LOG_FILE. I use parameter expansions because I want to set these variables only if they were not set already.
The problem is that MY_FILE becomes true but LOG_FILE throws an error:
script.sh: line 2: something.log: command not found
I could not find a way to assign the string as is, I tried with different options, simple quotes, and echoing it but nothing did the trick for me.
The parameters will always expand to a value, so you'll have to use them in a context where such an argument is ignored. Conveniently, : aka true does this:
: "${LOG_FILE:="something.log"}"
It only happens to work for your ${MY_FLAG:=true} because true (as discussed) is a valid command. If you run the script with MY_FLAG=date ./yourscript then you'll see that it actually runs date instead of just assigning a default.
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.
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.
For each of two examples below I'll try to explain what result I expected and what I got instead. I'm hoping for you to help me understand why I was wrong.
1)
VAR1=VAR2
$VAR1=FOO
result: -bash: VAR2=FOO: command not found
In the second line, $VAR1 gets expanded to VAR2, but why does Bash interpret the resulting VAR2=FOO as a command name rather than a variable assignment?
2)
'VAR=FOO'
result: -bash: VAR=FOO: command not found
Why do the quotes make Bash treat the variable assignment as a command name?
Could you please describe, step by step, how Bash processes my two examples?
How best to indirectly assign variables is adequately answered in other Q&A entries in this knowledgebase. Among those:
Indirect variable assignment in bash
Saving function output into a variable named in an argument
If that's what you actually intend to ask, then this question should be closed as a duplicate. I'm going to make a contrary assumption and focus on the literal question -- why your other approaches failed -- below.
What does the POSIX sh language specify as a valid assignment? Why does $var1=foo or 'var=foo' fail?
Background: On the POSIX sh specification
The POSIX shell command language specification is very specific about what constitutes an assignment, as quoted below:
4.21 Variable Assignment
In the shell command language, a word consisting of the following parts:
varname=value
When used in a context where assignment is defined to occur and at no other time, the value (representing a word or field) shall be assigned as the value of the variable denoted by varname.
The varname and value parts shall meet the requirements for a name and a word, respectively, except that they are delimited by the embedded unquoted equals-sign, in addition to other delimiters.
Also, from section 2.9.1, on Simple Commands, with emphasis added:
The words that are recognized as variable assignments or redirections according to Shell Grammar Rules are saved for processing in steps 3 and 4.
The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.
Redirections shall be performed as described in Redirection.
Each variable assignment shall be expanded for tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal prior to assigning the value.
Also, from the grammar:
If all the characters preceding '=' form a valid name (see the Base Definitions volume of IEEE Std 1003.1-2001, Section 3.230, Name), the token ASSIGNMENT_WORD shall be returned. (Quoted characters cannot participate in forming a valid name.)
Note from this:
The command must be recognized as an assignment at the very beginning of the parsing sequence, before any expansions (or quote removal!) have taken place.
The name must be a valid name. Literal quotes are not part of a valid variable name.
The equals sign must be unquoted. In your second example, the entire string was quoted.
Assignments are recognized before tilde expansion, parameter expansion, command substitution, etc.
Why $var1=foo fails to act as an assignment
As given in the grammar, all characters before the = in an assignment must be valid characters within a variable name for an assignment to be recognized. $ is not a valid character in a name. Because assignments are recognized in step 1 of simple command processing, before expansion takes place, the literal text $var1, not the value of that variable, is used for this matching.
Why 'var=foo' fails to act as an assignment
First, all characters before the = must be valid in variable names, and ' is not valid in a variable name.
Second, an assignment is only recognized if the = is not quoted.
1)
VAR1=VAR2
$VAR1=FOO
You want to use a variable name contained in a variable for the assignment. Bash syntax does not allow this. However, there is an easy workaround :
VAR1=VAR2
declare "$VAR1"=FOO
It works with local and export too.
2)
By using single quotes (double quotes would yield the same result), you are telling Bash that what is inside is a string and to treat it as a single entity. Since it is the first item on the line, Bash tries to find an alias, or shell builtin, or an executable file in its PATH, that would be named VAR=FOO. Not finding it, it tells you there is no such command.
An assignment is not a normal command. To perform an assignment contained in a quote, you would need to use eval, like so :
eval "$VAR1=FOO" # But please don't do that in real life
Most experienced bash programmers would probably tell you to avoid eval, as it has serious drawbacks, and I am giving it as an example just to recommend against its use : while in the example above it would not involve any security risk or error potential because the value of VAR1 is known and safe, there are many cases where an arbitrary (i.e. user-supplied) value could cause a crash or unexpected behavior. Quoting inside an eval statement is also more difficult and reduces readability.
You declare VAR2 earlier in the program, right?
If you are trying to assign the value of VAR2 to VAR1, then you need to make sure and use $ in front of VAR2, like so:
VAR1=$VAR2
That will set the value of VAR2 equal to VAR1, because when you utilize the $, you are saying that value that is stored in the variable. Otherwise it doesn't recognize it as a variable.
Basically, a variable that doesn't have a $ in front of it will be interpreted as a command. Any word will. That's why we have the $ to clarify "hey this is a variable".