Bash built-in, Variable Assignment through ${var:-""} - bash

Explain please, what does this construction mean:
foo=${bar:-"const"}
foo=${bar:+"const"} # not sure about using this construction at all
For example:
PATH=${PATH}:${BUILD_DIR:-"/SCA"}/tools
...
if [[ ${DEBUG:-""} = "ON" ]] ; then <...>; fi
I tried to look it on ABSG, tried to read man builtin, but it's still complicated for me now.
AFAIK, it is smth like assignment to $foo some value, with NULL-check of variable $bar.

${bar-const} evaluates to the string 'const' is bar is unset.
${bar:-const} evaluates to the string 'const' if bar is unset or the empty string.
${bar+const} evaluates to the string 'const' if bar is set.
${bar:+const} evaluates to the string 'const' if bar is set and not the empty string.
The latter two evaluate to the empty string otherwise, and the former two evaluate to $bar otherwise.

Related

Bash Subshell Expansion as Parameter to Function

I have a bash function that looks like this:
banner(){
someParam=$1
someOtherParam=$2
precedingParams=2
for i in $(seq 1 $precedingParams);do
shift
done
for i in $(seq 1 $(($(echo ${##}) - $precedingParams)));do
quotedStr=$1
shift
#do some stuff with quotedStr
done
}
This function, while not entirely relevant, will build a banner.
All params, after the initial 2, are quoted lines of text which can contain spaces.
The function fits each quoted string within the bounds of the banner making new lines where it sees fit.
However, each new parameter ensures a new line
My function works great and does what's expected, the problem, however, is in calling the function with dynamic parameters as shown below:
e.g. of call with standard static parameters:
banner 50 true "this is banner text and it will be properly fit within the bounds of the banner" "this is another line of banner text that will be forced to be brought onto a new line"
e.g. of call with dynamic parameter:
banner 50 true "This is the default text in banner" "$([ "$someBool" = "true" ] && echo "Some text that should only show up if bool is true")"
The problem is that if someBool is false, my function will still register the resulting "" as a param and create a new empty line in the banner.
As I'm writing this, I'm finding the solution obvious. I just need to check if -n $quotedStr before continuing in the function.
But, just out of blatant curiosity, why does bash behave this way (what I mean by this is, what is the process through which subshell expansion occurs in relation to parameter isolation to function calls based on quoted strings)
The reason I ask is because I have also tried the following to no avail:
banner 50 true "default str" $([ "$someBool" = "true" ] && echo \"h h h h\")
Thinking it would only bring the quotes down if someBool is true.
Indeed this is what happens, however, it doesn't properly capture the quoted string as one parameter.
Instead the function identifies the following parameters:
default str
"h
h
h
h"
When what I really want is:
default str
h h h h
I have tried so many different iterations of calls, again to no avail:
$([ "$someBool" = "true" ] && echo "h h h h")
$([ "$someBool" = "true" ] && echo \\\"h h h h\\\")
$([ "$someBool" = "true" ] && awk 'BEGIN{printf "%ch h h h h%c",34,34}')
All of which result in similar output as described above, never treating the expansion as a true quoted string parameter.
The reason making the command output quotes and/or escapes doesn't work is that command substitutions (like variable substitutions) treat the result as data, not as shell code, so shell syntax (quotes, escapes, shell operators, redirects, etc) aren't parsed. If it's double-quoted it's not parsed at all, and if it's not in double-quotes, it's subject to word splitting and wildcard expansion.
So double-quotes = no word splitting = no elimination of empty string, and no-double-quotes = word splitting without quote/escape interpretation. (You could do unpleasant things to IFS to semi-disable word splitting, but that's a horrible kluge and can cause other problems.)
Usually, the cleanest way to do things like this is to build a list of conditional arguments (or maybe all arguments) in an array:
bannerlines=("This is the default text in banner") # Parens make this an array
[ "$someBool" = "true" ] &&
bannerlines+=("Some text that should only show up if bool is true")
banner 50 true "${bannerlines[#]}"
The combination of double-quotes and [#] prevents word-splitting, but makes bash expand each array element as a separate item, which is what you want. Note that if the array has zero elements, this'll expand to zero arguments (but be aware that an empty array, like bannerlines=() is different from an array with an empty element, like bannerlines=("")).

why integer result in 0 when I change declared integer to string in Bash script

Code:
#!/bin/bash
declare -i number
# The script will treat subsequent occurrences of "number" as an integer.
number=3
echo "Number = $number" # Number = 3
number=three
echo "Number = $number" # Number = 0
# Tries to evaluate the string "three" as an integer.
I cannot figure out why number changed when I assign a string "three" to number. I think number should stay the same. That really surprised me.
From the declare section of man bash:
-i The variable is treated as an integer; arithmetic evaluation (see ARITHMETIC EVALUATION) is performed when the variable is assigned a value.
From the ARITHMETIC EVALUATION section of man bash:
The value of a variable is evaluated as an arithmetic expression when...a variable which has been given the integer attribute using declare -i is assigned a value. A null value evaluates to 0.
Together, these clearly state that the behavior you're seeing is the expected behavior. When the characters t h r e e are evaluated arithmetically, the resulting null value is evaluated as 0, which is then assigned to the variable number.
All assignments in bash are interpreted first as strings. number=10 interprets the 1 0 as a string first, recognizes it as a valid integer, and leaves it as-is. number=three is just as syntactically and semantically valid as number=10, which is why your script continues without any error after assigning the evaluated value of 0 to number.

how to understand the x in ${CONFIG+x}

The code is below,
if [ -z ${CONFIG+x} ]; then
CONFIG=/usr/local/etc/config.ini
else
CONFIG=$(realpath $CONFIG)
fi
Can someone tell me what "x" exactly mean?
It means that if the variable $CONFIG is set, use the value x, otherwise use the null (empty) string.
The test -z checks whether the following argument is empty, so if $CONFIG is not set, the if branch will be taken.
Testing it out (with $a previously unset):
$ echo "a: '${a+x}'"
a: ''
$ a='any other value'
$ echo "a: '${a+x}'"
a: 'x'
This is one of the parameter expansion features defined for the POSIX shell. The simplest is just a variable which is substituted, ${parameter}, while the others have an embedded operator telling what to do with an optional parameter versus an optional word, e.g, "${parameterOPword}"
For this particular case, parameter is CONFIG, OP is + and word is x:
if the parameter is Set and Not Null, the word's value is used, otherwise
if the parameter is Set and Null, the word's value is used, otherwise
a null value is used
A null value for a shell variable is from an explicit assignment, e.g.,
CONFIG=
If a variable has never been assigned to, it is unset. You can make a variable unset using that keyword:
unset CONFIG
There are separate tests for Set and Not Null and Set and Null because the standard lists eight operators. For six of those, the parameter's value would be used if it is Set and Not Null.
In substitution, you cannot see a difference between an unset and null value. Some shells (such as bash) define additional types of parameter expansion; this question is more general.

Any language with syntactic sugar for assign variable if not defined?

In almost every language I tend to write something that sets a variable to a default value if it's not defined. Everytime I get surprised why the syntax is not simpler, e.g. why I have to write the variable name twice just to set it to a default value. For example, in Perl:
my $var;
# some code here...
$var = "default" unless $var; # $var typed twice
Or in C:
char *var = NULL;
// some code here...
if (!var) // var typed
var = "default"; // twice
Why not have some syntactic sugar that sets the variable if it's not defined? In perl it could look like
$var ?= "default";
I am just curious, are there any languages out there that in fact have syntactic sugar for this?
bash supports the parameter expansion and testing.
e.g. if $1 is defined AND NOT EMPTY, use $1; otherwise, set to "text", enter:
output=${1-text}

Complicated bash variable syntax

In one bash script i found the next construction:
if [[ "${xvar[id]:0:${#cnt}}" != "$cnt" ]]; then
Can someone explain what the above condition does?
The complicated expression is: ${xvar[id]:0:${#cnt}}.
$xvar must be an array, possibly associative. If it is associative, the part ${xvar[id]} refers to the element of the array identified by the string 'id'; if not, then it refers to the element indexed by variable $id (you're allowed to omit the nested $), as noted by chepner in a comment.
The ${xxx:0:${#cnt}} part of the expression refers to a substring from offset 0 to the length of the variable $cnt (so ${#cnt} is the length of the string in the variable $cnt).
All in all, the test checks whether the first characters of ${xvar[id]} are the same as the value of $cnt, so is the value in $cnt a prefix of the value in ${xvar[id]}.

Resources