Shell - use of the variable with dash - shell

I was reading tcollector init.sh file here: https://github.com/OpenTSDB/tcollector/blob/master/rpm/initd.sh#L25
what does the dash mean in the line 25TCOLLECTOR=${TCOLLECTOR-/usr/local/tcollector/tcollector.py}?
(I originally thought it just assigns the path after the dash to TCOLLECTOR; however my tests show two different results:
if TCOLLECTOR has already been assigned a value, it will conserve that value
else TCOLLECTOR will have the value "/usr/local/tcollector/tcollector.py"
I also looked at the use of "-" but it's all about STDIN and STDOUT...I didn't get a clue of how they are related to my question.)
Thank you.

That's an example of parameter expansion; the general POSIX variety is documented here, and you can read about the Bash incarnation here.
Basically, the minus sign expansion does exactly what you described: ${anyVariable-anyExpression} expands to the value of $anyVariable if it is set, but if it's not set, then it expands to anyExpression.
The plus sign does exactly the opposite: ${anyVariable+anyExpression} expands to anyExpression if $anyVariable has a value, and to nothing (the empty string) if it is unset.
There are several other options as well.

Related

bash array slicing strange syntax in perl path: `${PATH:+:${PATH}}"`

On Linux Ubuntu, when you do sudo apt update && sudo apt install perl, it adds the following to the bottom of your ~/.bashrc file (at least, many months later, I think that is what added those lines):
PATH="/home/gabriel/perl5/bin${PATH:+:${PATH}}"; export PATH;
PERL5LIB="/home/gabriel/perl5/lib/perl5${PERL5LIB:+:${PERL5LIB}}"; export PERL5LIB;
PERL_LOCAL_LIB_ROOT="/home/gabriel/perl5${PERL_LOCAL_LIB_ROOT:+:${PERL_LOCAL_LIB_ROOT}}"; export PERL_LOCAL_LIB_ROOT;
PERL_MB_OPT="--install_base \"/home/gabriel/perl5\""; export PERL_MB_OPT;
PERL_MM_OPT="INSTALL_BASE=/home/gabriel/perl5"; export PERL_MM_OPT;
What does this strange syntax do in many of the lines, including in the first line? It appears to be some sort of bash array slicing:
${PATH:+:${PATH}}
The ${PATH} part is pretty straightforward: it reads the contents of the PATH variable, but the rest is pretty cryptic to me.
It's not array slicing; it's a use of one of the POSIX parameter expansion operators. From the bash man page, in the Parameter Expansions section,
${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is
substituted, otherwise the expansion of word is substituted.
It's a complex way of making sure that you only add a : to the value if PATH isn't empty to start with. A longer, clearer way of writing it would be
if [ -n "$PATH" ]; then
PATH=/home/gabriel/perl5/bin:$PATH
else
PATH=/home/gabriel/perl5/bin
fi
However, since it if almost inconceivable that PATH is empty when .basrhc is sourced, it would be simpler to just prepend the new path and be done with it.
PATH=/home/gabriel/perl5/bin:$PATH
If PATH actually ended with a :, it would implicitly include the current working directory in the search path, which isn't a good idea for security reasons. Also from the bash man page, in the section on Shell Variables under the entry for PATH:
A zero-length (null) directory name in the
value of PATH indicates the current directory. A null directory
name may appear as two adjacent colons, or as an initial or
trailing colon.
As an aside, it's good to understand what various installers try to add to your shell configuration. It's not always necessary, and sometimes can actively change something you already have configure.
I would much prefer if packages simply printed instructions for what needs to be added to your configuration (and why), and leave it to the user to make the appropriate modifications.
What does this strange syntax do in many of the lines, including in the first line?
It's the ${parameter:+word} form of parameter expansion where word becomes the expanded value if parameter is not unset and not having the value of an empty string (a.k.a. null).

Why expanding on left hand side arg is leading to error

I accidentally wrote next assignment in one of my scripts:
$X=$(echo 'astring')
which fails with =astring: command not found.
The correct and intended assignment was X=$(echo 'astring') which works and sets X='astring'.
The question is what happens with the first one? Is $ trying to execute the result of the right hand side? And if that is so then why is it also incorporating = in it? I'm confused.
The behaviour of $X=$(echo 'astring') depends on the contents of $X. When it's empty (which it probably was), it expands to an empty string, and the remaining string is interpreted as a command
$X=$(echo 'astring')
=astring
If $X contains something, e.g. "astring", the string is expanded to
astring=astring
But it doesn't set the $astring variable as one might think, because of the order of expansions. Assignments are identified before any expansion happens. So, it's interpreted as a command again
astring=astring: command not found

Bash - Why does $VAR1=FOO or 'VAR=FOO' (with quotes) return command not found?

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

Error in string Concatenation in Shell Scripting

I am beginner to Shell scripting.
I have used a variable to store value A="MyScript". I tried to concatenate the string in subsequent steps $A_new. To my surprise it didn't work and $A.new worked.
Could you please help me in understanding these details?
Thanks
Shell variable names are composed of alphabetic characters, numbers and underscores.
3.231 Name
In the shell command language, a word consisting solely of underscores, digits, and alphabetics from the portable character set. The first character of a name is not a digit.
So when you wrote $A_new the shell interpreted the underscore (and new) as part of the variable name and expanded the variable A_new.
A period is not valid in a variable name so when the shell parsed $A.new for a variable to expand it stopped at the period and expanded the A variable.
The ${A} syntax is designed to allow this to work as intended here.
You can use any of the following to have this work correctly (in rough order of preferability):
echo "${A}_new"
echo "$A"_new
echo $A\_new
The last is least desirable because you can't quote the whole string (or the \ doesn't get removed. So since you should basically always quote your variable expansions you would end up probably doing echo "$A"\_new but that's no different then point 2 ultimately so why bother.
This happens because the underscore is the valid character in variable names.
Try this way:
${A}_new or "$A"_new
The name of a variable can contain letters ( a to z or A to Z), numbers ( 0 to 9) or the underscore character ( _).
Shell does not require any variable declaration as in programming languages as C , C++ or java. So when you write $A_new shell consider A_new as a variable, which you have not assigned any value therefore it comes to be null.
To achieve what you mentioned use as :
${A}_new
Its always a good practice to enclose variable names in braces after $ sign to avoid such situation.

Lookup shell variables by name, indirectly [duplicate]

This question already has answers here:
How to use a variable's value as another variable's name in bash [duplicate]
(6 answers)
Closed 5 years ago.
Let's say I have a variable's name stored in another variable:
myvar=123
varname=myvar
Now, I'd like to get 123 by just using $varname variable.
Is there a direct way for that? I found no such bash builtin for lookup by name, so came up with this:
function var { v="\$$1"; eval "echo "$v; }
so
var $varname # gives 123
Which doesn't look too bad in the end, but I'm wondering if I missed something more obvious.
From the man page of bash:
${!varname}
If the first character of parameter is an exclamation point, a level of
variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable;
this variable is then expanded and that value is used in the rest of
the substitution, rather than the value of parameter itself. This is
known as indirect expansion.
There isn't a direct Posix-conforming syntax, only a bashism. I usually do this:
eval t="\$$varname"
This will work on any Posix shell, including those systems where bash is the login shell and /bin/sh is something smaller and faster like ash. I like bash and use it for my login shell but I avoid bashisms in command files.
Note: One problem with writing bash-specific scripts is that even if you can count on bash being installed, it could be anywhere on the path. It might be a good idea in that case to use the fully general /usr/bin/env shebang style, but note that this is still not 100% portable and has security issues.
${!varname} should do the trick
$ var="content"
$ myvar=var
$ echo ${!myvar}
content
I usually look at Advance Bash-Scripting Guide when I need to freshen up my Bash skills.
Regarding your question look at Indirect References
Notation is:
Version < 2
\$$var
Version >= 2
${!varname}
# bmuSetIndirectVar()
# TO DOUBLE CHECK THIS COMMENT AND DEMO
# This function is an helper to read indirect variables.
# i.e. get the content of a variable whose name is saved
# within an other variable. Like:
# MYDIR="/tmp"
# WHICHDIR="MYDIR"
# bmuSetIndirectVar "WHICHDIR" "$MYDIR"
#
bmuSetIndirectVar(){
tmpVarName=$1
locVarName=$1
extVarName=$2
#echo "debug Ind Input >$1< >$2<"
eval tmpVarName=\$$extVarName
#echo "debug Ind Output >$tmpVarName< >$extVarName<"
export $locVarName="${tmpVarName}"
}
I am currently using this little function. I am not fully happy with it, and I have seen different solutions on the web (if I could recall I would write them here), but it seems to work. Within these few lines there is already some redundancy and extra data but it was helpful for debugging.
If you want to see it in place, i.e. where I am using it, check:
https://github.com/mariotti/bmu/blob/master/bin/backmeup.shellfunctions.sh
Of course it is not the best solution, but made me going on with the work, in
the hope I can replace it with something a bit more general soon.

Resources