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

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.

Related

Combine a variable and string and get the value of the variable formed in a single line

I was writing a script where I came across a situation.
Audio_Repo = "/src/audio_123";
Audio_ImgTag = "aud021882";
Audio_Enable = 1;
.....
Video_Repo = "/src/vid_823";
Video_ImgTag = "video9282";
Video_Enable = 0;
....
#Say proj_var ="Audio"
#it could be either Audio or Video based on some conditional check
....
proj_var = "Audio"
....
PROJECT_REPO= ${!{$proj_var"_Repo"}}
#PROJECT_REPO should hold the value "src/audio_123"
But the above representation throws bad substitution error
I know that I could use a temporary variable as follows
temp= $proj_var"_Repo";
PROJECT_REPO = ${!temp};
But I have many properties and I do not want to use temporary variables for each of them. Instead I want single line substitutions.
One way to do it is to use eval:
#! /bin/bash -p
Audio_Repo="/src/audio_123"
Audio_ImgTag=aud021882
Audio_Enable=1
# ...
Video_Repo=/src/vid_823
Video_ImgTag=video9282
Video_Enable=0
# ....
# Say proj_var="Audio"
# it could be either Audio or Video based on some conditional check
# ....
proj_var="Audio"
# ....
eval "Project_Repo=\${${proj_var}_Repo}"
# Project_Repo should hold the value "src/audio_123"
printf '%s\n' "$Project_Repo"
The code prints /src/audio_123.
eval is dangerous, and should be avoided if possible. See Why should eval be avoided in Bash, and what should I use instead?. In this case the temporary variable, despite the increased verbosity, is a better option.
I replaced PROJECT_REPO with Project_Repo to avoid possible a possible clash with an environment variable. See Correct Bash and shell script variable capitalization.
I've fixed some Bash syntax issues in the code in the question. Spaces around = are errors. Semicolons at the ends of lines are unnecessary.
Shellcheck issues some warnings for the code, but they are all harmless.
Another option is to use a helper function:
# ...
# Set the value of the variable whose name is in the first parameter ($1)
# to the value of the variable whose name is in the second parameter ($2).
function setn { printf -v "$1" '%s' "${!2}" ; }
# ...
setn Project_Repo "${proj_var}_Repo"
Using the setn (a poor name, choose a better one) function avoids both a temporary variable and eval.
Uses arrays, not variable names you need to manipulate.
Repo=0
ImgTag=1
Enable=2
Audio=(/src/audio_123 aud021882 1)
Video=(/src/vid_823 video9282 0)
proj_repo=Audio[$Repo]
project_var=${!proj_repo}

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.

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

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.

Add up parameters in for loop

I've implemented a function which contains a while for loop defined as follows:
func()
{
...
for i in "$#"; do
enum="source"
sourceID=$i
ret_ID $FILE_NAME $sourceID $enum
ID=$req_ID
((ID+=ID))
done
echo $ID
}
The function ret_ID parses a file which contains some variables as
param1=0x00000001
param2=0x00000400
param3=0x000008000
...
No matter how many parameters I pass, echo $ID returns the ID associated with the last parameter and not the sum of all of them. For instance, func param1 param3 returns 32768 and not 32769.
Update: Judging by a comment by the OP, it sounds like glenn jackman's recommendation to switch from letting ret_ID() set a global variable in order to return its result to outputting its result to stdout and capturing the result in a command substitution ($(...)) solved the problem.
Assuming that the problem wasn't the simple logic error Glenn points out (((ID+=ID)) should be ((ID+=req_ID )): The exact cause of the original problem is not known, but since both func() and ret_ID() operate on global variables, it's easy to see how one function could interfere with the other, such as if ret_ID() accidentally also sets variable $ID.
Here's a rewrite of the function that shows this change, and also suggests a few other changes to make the function more robust, most notably the use of local variables:
func()
{
# Declare *local* variables.
local arg enum sourceID retID
# Declare the local result variable *as an integer*
# Also, better to use variable names that at least start with a *lowercase*
# letter to avoid conflicts with *environment* variables.
local -i id=0
# ...
for arg; do # loop over all args; the `in "$#"` part is optional
enum="source"
sourceID=$arg
# Call helper function and let it return its result via *stdout* captured
# through a command substitution rather than by setting a global variable.
# Note the use of double quotes to prevent problems with values with embedded spaces.
reqID=$(ret_ID "$FILE_NAME" "$sourceID" "$enum")
# Add the value returned to $id
# Note that since $id was declared as an integer,
# use of (( ... )) is optional.
id+=$reqID
done
echo "$id"
}

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