How does "FOO= myprogram" in bash make "if(getent("FOO"))" return true in C? - bash

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.

Related

Unset is not useful? (Korn shell)

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.

Bash: usage of `true`

In many scripts I've inherited from a former employee I keep seeing this pattern:
if (true $SOME_VAR)&>/dev/null; then
...
fi
or this one
(true $SOME_VAR)&>/dev/null || SOME_VAR="..."
The man page for true says it always returns true, hence I keep wondering, what is the point of these checks? In the first case the then part is always executed, in the second case the right hand part is never executed.
If set -u (a.k.a. set -o nounset) is in effect, true $SOME_VAR will fail when $SOME_VAR is not defined. This is therefore a way to test whether the variable is defined.
To complement jwodder's helpful answer and Fred's helpful answer:
In Bash v4.2+
, the less obscure and more efficient -v operator can be used to test if a variable is defined[1] (note that no $ must be used):
[[ -v SOME_VAR ]]
In older Bash versions and in POSIX-compliant scripts, use Fred's parameter-expansion-based approach, which is also more efficient than the (true ...) approach.
If the intent is to simply provide a default value, as in the (true $SOME_VAR)&>/dev/null || SOME_VAR="..." idiom, use the (POSIX-compliant) technique suggested by kojiro, also based on a parameter expansion:
SOME_VAR=${SOME_VAR-...} # keep $SOME_VAR value or default to '...'
Toby Speight suggests another POSIX-compliant variant, ${SOME_VAR=...}, which directly updates the variable with the default value, if it is undefined; however, it has the side effect of expanding to the (resulting) value - which may or may not be desired. A concise, but also slightly obscure way to suppress the expansion is to pass the expansion to the colon (null) utility (:), which expands, but otherwise ignores its arguments (compared to using true for the same purpose, it is perhaps slightly less confusing):
: ${SOME_VAR=...} # set $SOMEVAR to '...' only if not defined
Note that all parameter expansions shown/mentioned above have a variant that places : before the operator, which then acts not only when the variable is undefined, but also when it is defined but empty (contains the null string):
${SOME_VAR:+...}, ${SOME_VAR:-...}, ${SOME_VAR:=...}
Arguably, this variant behavior is the generally more robust technique, especially given that when set -u (set -o nunset) is not turned on, undefined variables expand to the null (empty) string.
To add to jwodder's explanation:
The use of (...) around true $SOME_VAR to create a subshell is crucial for this somewhat obscure test for variable existence to work as intended.
Without a subshell, the entire script would abort.
The need for a subshell makes the technique not just obscure, but also inefficient (although that won't really be noticeable with occasional use).
Additionally, if set -u (set -o nounset) happens not to be in effect, the technique treats all variables as defined.
With the subshell, only the subshell aborts, which is reflected in its exit code to the current shell: 1, if the subshell aborted (the variable doesn't exist), 0 otherwise.
Therefore, the (true ...) command only evaluates to (conceptually) true if the variable exists.
&>/dev/null suppresses the error message from the subshell that is emitted if the variable doesn't exist.
As an aside: true never produces no output, so it is sufficient to use (true $SOME_VAR)2>/dev/null (suppress stderr only) - this change makes the technique POSIX-compliant (though still not advisable).
It isn't just set -u (set -o nounset) statements inside a script that turn on aborting in case of access to an undefined variable - invoking bash explicitly with command-line option -u has the same effect.
[1] Since Bash v4.3, you can also test whether an array variable has an element with the specified index; e.g.:
a=( one two ); [[ -v a[0] ]] succeeds, because an array element with index 0 exists; works analogously with associative arrays.
The following is probably equivalent, and more straightforward :
if [ "${SOME_VAR+x}" ] then
...
fi
Or, in the assignment case :
[ "${SOME_VAR+x}" ] || SOME_VAR="..."
The + expansion operator expands to a null string if the variable is unset, and to x if it is assigned (assigned a null string still means assigned). In this case, you could replace x by whatever you want (except a null string).
There is also a ${SOME_VAR:+x} variant. The difference is with null strings : :+ expands to a null string if the variable is assigned a null string (while + expands to x if the value is assigned, even if it is a null string).
While not strictly the same,
if [ x"$SOME_VAR" = x ]; then
...
fi
tends to do what you want; that is the if is true if $SOME_VAR is undefined or (difference:) defined to be the zero-length string.
This code does not work if SOME_VAR is unset and -u is set. I believe the following bashism works though: "${SOME_VAR-}" = "".

What is the purpose of setting a variable default to empty in bash?

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.

Bash assignment value to variable as command substitution and print value output

I would like to achieve this in Bash: echo $(a=1)and print the value of variable a
I test eval, $$a,{}, $() but none of them work as most of the times either I got literally a=1 or in one case (I don't remember which) it tried to execute the value.
I known that I can do: a=1;echo $a but because I'm little fun one command per line (even if sometimes is getting little complicated) I was wondering if is possible to do this either with echo or with printf
If you know that $a is previously unset, you can do this using the following syntax:
echo ${a:=1}
This, and other types of parameter expansion, are defined in the POSIX shell command language specification.
If you want to assign a numeric value, another option, which doesn't depend on the value previously being unset, would be to use an arithmetic expansion:
echo $(( a = 1 ))
This assigns the value and echoes the number that has been assigned.
It's worth mentioning that what you're trying to do cannot be done in a subshell by design because a child process cannot modify the environment of its parent.

Boolean in Shell Scripting

I have a problem with boolean for while loop. As such, I switch to for loop instead.
But still, I cannot change the value of a boolean after the condition is met.
doFirst= true
for (( j=1; j<=7; j++))
do
letter="A"
seatChoses=$letter$j
flagRand=$(echo $flightSeatBooked | awk -v flseatRand=$flightSeatBooked -v orseatRand=$seatChoses '{print match(flseatRand, orseatRand)}')
if $doFirst ; then
**$doFirst= false** // Here is the error!
if [ $flagRand -eq 0 ]; then
echo "System generated a slot, "$seatChoses" for you. [Y or N]"
fi
fi
done
There is no such thing as a boolean value in a shell script (that is, something you can store in a variable, and treat as a boolean). true and false are commands; true exits with value 0, and false exits with a nonzero value. An if statement in bash taks a command; if that command returns 0, then the then clause is executed, otherwise the else clause is.
doFirst= true
This line doesn't do what you expect at all. In a shell script, you cannot have any spaces after the equals sign. The space means you're done with the assignment, and now writing a command. This is equivalent to:
doFirst="" true
Furthermore, if you have an assignment before a command (like this), that doesn't actually perform the assignment in the shell. That sets that environment variable in the environment for that command alone; the assignment has no effect on anything outside of that command.
if $doFirst ; then
This expands the $doFirst variable, and tries to interpret the result as a command. Oddly, if $doFirst is undefined (which it is, as I explain above), this takes the then branch. At that point, you make your first mistake again, trying to set a variable to be false, and again, nothing happens; $doFirst is left undefined. You make the further mistake of trying to assign $doFirst; you use $ to get the value of a variable, when setting, you use the bare name.
My recommendation would be to not try to use booleans in Bash; just use strings instead, and check the value of the string. Note that I remove the space, so now I'm setting it to that exact string; and there is no command, so this sets the variable within the shell, not in the environment for a single command:
doFirst=true
# ...
if [ $doFirst = true ]; then
doFirst=false
# ...
Are you actually putting a space between the = and the "true"/"false" or is that a formatting error? That's one of your problems.
Another, as mentioned by Anders Lindahl in the comment section, is that when you set a variable in shell scripting, you cannot use the $ in the front. You must say
doFirst=false
Again, note that there are no spaces around the equals sign.

Resources