Bash: usage of `true` - bash

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-}" = "".

Related

Is this if condition equivalent to testing the existence of a variable? [duplicate]

This question already has answers here:
What does the '-' (dash) after variable names do here?
(3 answers)
Closed 2 years ago.
I'm digging around in some the system files on my Mac. In /etc/profile I've found the following excerpt:
if [ "${BASH-no}" != "no" ]; then
[ -r /etc/bashrc ] && . /etc/bashrc
fi
I've tried echo "${x-no}" for various choices of x and it seems like it's printing the value of x whenever x exists (i.e. has been set), and no otherwise.
Which leads me to wonder: Is this condition simply testing whether the variable x has been set?
Further questions: What exactly does - do? Is there a better way to test whether a variable has been set?
The meaning of ${BASH-no} is documented in §2.6.2, Parameter Expansion, of the Single Unix Specification:
${parameter:-[word]}
Use Default Values. If parameter is unset or null, the expansion of word (or an empty string if word is omitted) shall be substituted; otherwise, the value of parameter shall be substituted.
The meaning when the colon is omitted (as in your example) is described slightly later:
In the parameter expansions shown previously, use of the in the format shall result in a test for a parameter that is unset or null; omission of the shall result in a test for a parameter that is only unset.
Then there is a table, which may be easier to understand. Here are the relevant rows:
parameterSet and Not Null
parameterSet but Null
parameterUnset
${parameter:-word}
substitute parameter
substitute word
substitute word
${parameter-word}
substitute parameter
substitute null
substitute word
Here is a reliable, portable way to check whether a variable is not set at all. Note that I am using a + modifier instead of a - modifier in the parameter expansion:
if [ "${BASH+set}" = "" ]; then
echo 'BASH not set at all'
else
echo 'BASH is set, perhaps to the empty string'
fi
The expansion of "${BASH+set}" can only be "" if BASH is entirely unset. If BASH is set, even to the empty string, then "${BASH+set}" expands to "set" instead.
Is this condition simply testing whether the variable x has been set?
Yes, though it gets confused in the unlikely event that BASH=no.
What exactly does - do?
Here's man bash:
[...] Omitting the colon results in a test only for a parameter that is un‐set.
${parameter:-word}
Use Default Values. If parameter is unset or null (see above),
the expansion of word is substituted. Otherwise, the
value of parameter is substituted.
Is there a better way to test whether a variable has been set?
Yes: [ -v BASH ]. However, this is bash specific, so it defeats the purpose of checking if the current shell is bash before doing bash specific operations.

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

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.

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.

Bash Script Understanding

I'm trying to figure what exactly is the bash code mentioned below trying to do, specially the [-z $M ] part. here M is a variable with a value
if [ -z $M ] ; then
can not find module directory
exit 1
man test Enter
press /-zEnter
you see:
-z STRING
the length of STRING is zero
so your script does, if $M length==0, then exit with status code 1
As others have said, it's using the test command (aka [) to check whether a string is blank. At least, that's what it's trying to do; because the string ($M) isn't double-quoted, it's actually doing something slightly different. Without double-quotes, the value of $M undergoes word splitting and wildcard expansion after it's replaced, so it might not be treated as a simple string (which the -z operator works on) with ... potentially unexpected consequences. Let me run through some of the possibilities:
If the value of $M is a single word (non-blank) without wildcards (* and ?), everything works as expected.
If the value of $M is zero-length (blank), the test command only sees a single argument (-z); when test is only given a single argument, it simply tests whether it's blank -- it's not, so it evaluates to true.
This happens to be the expected result in this case, but it's purely by coincidence, and with many other operators it wouldn't be the right result. For instance, [ -n $M ] (which looks like it should test whether $M is *non*blank), [ -e $M ] (which looks like it should test whether $M is the name of a file/directory) etc will all evaluate to true if $M is blank.
If the value of $M consists entirely of whitespace (but isn't empty), it gets eliminated before test sees it, and test evaluates to true (see previous case). This might or might not be what the scripter had in mind.
If the value of $M has multiple words, test will attempt to evaluate it as (part of) an expression. It will probably not be a valid expression, in which case test will print an error and return false (which is right ... sort of).
On the other hand, if it is a valid expression... Suppose for example you had, M='= -z; test would evaluate the expression -z = -z which would be true, not at all what the scripter had in mind.
If the value of $M has any wildcards, the shell will try to match them against files and pass test the list of matches; it'll try to evaluate them as an expression (see previous case), probably giving an error and returning false (again, sort of right).
Mind you, if you happen to have set the nullglob shell option and the wildcard doesn't match any files, the shell will replace it with null, and the script will act as though "u*n*m*a*t*c*h*e*d" was the empty string.
The lesson here: if you don't want your scripts to behave in weird and unexpected ways, double-quote your variable references!
The [ is actually a standard Unix command (probably implemented internally in Bash, but available whatever shell you are using). It is an alias for the command test, so its manual entry can be found by typing man test. Here's an online copy of that manual page.
When invoked as [, test will generally expect its last argument to be a ], just for good looks, so [ -z $M ] is equivalent to test -z $M.
In this case, the -z argument causes test to return true if the following argument is a string of length zero. The variable $M, defined further up the script, can thus be tested for a valid value.
It checks whether the content of variable M is an empty string.
Check this link

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