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

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.

Related

Extracting git commit information in GitHub action workflow- use of '$' symbol [duplicate]

This question already has answers here:
Backticks vs braces in Bash
(3 answers)
Brackets ${}, $(), $[] difference and usage in bash
(1 answer)
Closed 4 years ago.
I have two questions and could use some help understanding them.
What is the difference between ${} and $()? I understand that ()
means running command in separate shell and placing $ means passing
the value to variable. Can someone help me in understanding
this? Please correct me if I am wrong.
If we can use for ((i=0;i<10;i++)); do echo $i; done and it works fine then why can't I use it as while ((i=0;i<10;i++)); do echo $i; done? What is the difference in execution cycle for both?
The syntax is token-level, so the meaning of the dollar sign depends on the token it's in. The expression $(command) is a modern synonym for `command` which stands for command substitution; it means run command and put its output here. So
echo "Today is $(date). A fine day."
will run the date command and include its output in the argument to echo. The parentheses are unrelated to the syntax for running a command in a subshell, although they have something in common (the command substitution also runs in a separate subshell).
By contrast, ${variable} is just a disambiguation mechanism, so you can say ${var}text when you mean the contents of the variable var, followed by text (as opposed to $vartext which means the contents of the variable vartext).
The while loop expects a single argument which should evaluate to true or false (or actually multiple, where the last one's truth value is examined -- thanks Jonathan Leffler for pointing this out); when it's false, the loop is no longer executed. The for loop iterates over a list of items and binds each to a loop variable in turn; the syntax you refer to is one (rather generalized) way to express a loop over a range of arithmetic values.
A for loop like that can be rephrased as a while loop. The expression
for ((init; check; step)); do
body
done
is equivalent to
init
while check; do
body
step
done
It makes sense to keep all the loop control in one place for legibility; but as you can see when it's expressed like this, the for loop does quite a bit more than the while loop.
Of course, this syntax is Bash-specific; classic Bourne shell only has
for variable in token1 token2 ...; do
(Somewhat more elegantly, you could avoid the echo in the first example as long as you are sure that your argument string doesn't contain any % format codes:
date +'Today is %c. A fine day.'
Avoiding a process where you can is an important consideration, even though it doesn't make a lot of difference in this isolated example.)
$() means: "first evaluate this, and then evaluate the rest of the line".
Ex :
echo $(pwd)/myFile.txt
will be interpreted as
echo /my/path/myFile.txt
On the other hand ${} expands a variable.
Ex:
MY_VAR=toto
echo ${MY_VAR}/myFile.txt
will be interpreted as
echo toto/myFile.txt
Why can't I use it as bash$ while ((i=0;i<10;i++)); do echo $i; done
I'm afraid the answer is just that the bash syntax for while just isn't the same as the syntax for for.
your understanding is right. For detailed info on {} see bash ref - parameter expansion
'for' and 'while' have different syntax and offer different styles of programmer control for an iteration. Most non-asm languages offer a similar syntax.
With while, you would probably write i=0; while [ $i -lt 10 ]; do echo $i; i=$(( i + 1 )); done in essence manage everything about the iteration yourself

Equal/minus sign without a colon in a parameter expasion in bash

I found a snippet like this in a Bash script recently:
$ echo ${A=3}
Now, I know that ${A:=3} would set the variable A if A is "falsy", or ${A:-3} would return 3 if A is "falsy." I have never seen these similar expressions without the colon though, and I cannot find the explanation for these colon-less expressions in the Bash's documentation.
What is going on here?
Actually, the documentation does explain what is going on here, even if burying the lede a bit:
When not performing substring expansion, using the form described below (e.g., ‘:-’), Bash tests for a parameter that is unset or null. Omitting the colon results in a test only for a parameter that is unset. Put another way, if the colon is included, the operator tests for both parameters’ existence and that its value is not null; if the colon is omitted, the operator tests only for existence.
In practice, this means that they behave the same way if the variables are unset:
$ echo ${A=no-colon}
no-colon
$ echo ${B:=with-colon}
with-colon
$ echo $A
no-colon
$ echo $B
with-colon
However, if the variables are set to the empty string, then the behavior is different. The expression with a colon will set the variable and return the value, and the one without will leave the variable as is (i.e., set to the empty string) and return its empty value:
$ A='' ; B=''
$ echo ${A=no-colon}
$ echo ${B:=with-colon}
with-colon
$ echo $A
$ echo $B
with-colon
As stated in the documentation, the same behavior applies to the other "operators" (-, ?, +).
Posting it in the spirit of Can I answer my own question? and because it took a surprisingly long time for me to learn it, even after finding it in code. Maybe making it a bit more explicit, with some examples, can help somebody else out there :)

eval not working in shellscript

I am trying to get the value of a variable to be selected by name at runtime, using eval, but I don't get its value if - (hyphen) is in the name.
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=\${${DBUSERNAME}}"
eval "PASSWORD=\${${DBPASSWORD}}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
RESULT
echo USERNAME=east-1
echo PASSWORD=east-1
EXPECTED RESULT
echo USERNAME=DB_USER_dev_us-east-1
echo PASSWORD=DB_USER_dev_us-east-1
It's working fine if there is no hyphen present in the name.
Investigation
We can see what's happening by running this in shell with -x option to trace execution:
$ sh -x ./36332134.sh
+ ENV=dev
+ REGION=us-east-1
+ DBUSERNAME=DB_USER_dev_us-east-1
+ DBPASSWORD=DB_PASS_dev_us-east-1
+ eval USERNAME=${DB_USER_dev_us-east-1}
+ USERNAME=east-1
+ eval PASSWORD=${DB_PASS_dev_us-east-1}
+ PASSWORD=east-1
+ echo USERNAME=east-1
USERNAME=east-1
+ echo PASSWORD=east-1
PASSWORD=east-1
Notice that eval USERNAME=${DB_USER_dev_us-east-1} gives us USERNAME=east-1. That's parameter expansion in effect, as described in the Bash manual:
When not performing substring expansion, using the forms documented below (e.g., :-), bash tests for a parameter that is unset
or null. Omitting the colon results in a test only for a parameter
that is unset.
${parameter:-word}
Use Default Values. If parameter is unset or null, the expansion of word is substituted. Otherwise, the value of parameter
is substituted.
Since $DB_USER_dev_us is unset, then the expansion of ${DB_USER_dev_us-east-1} is east-1.
Workarounds
Shell doesn't allow - in variable names (including environment variables). I guess DB_USER_dev_us-east-1 was set by some non-shell program? In which case, you'll need a similar non-shell program to retrieve it, I think. I tested quoting the -, but to no avail.
If you can use Bash as your shell, you might want to use an associative array instead of composing variable names.
If you are able to change the environment variables, you might consider changing the - to (say) _, then using (Bash) ${REGION//-/_} or (otherwise) tr to transform the name:
REGION="${REGION//-/_}" # Bash
REGION="$(echo "$REGION"|tr - _)" # POSIX
You are evaluating/expanding a few too many times.
Toby's answer is exactly correct (and shows the proper debugging technique for this sort of issue) but the solution to the problem is to unwrap one level of expansion.
You wrote
eval "USERNAME=\${${DBUSERNAME}}"
which becomes
eval "USERNAME=\${DB_USER_dev_us-east-1}"
which then gets run through eval as
USERNAME=${DB_USER_dev_us-east-1}
which becomes
USERNAME=east-1
but you wanted to stop after the first expansion. That is
eval "USERNAME=\${DB_USER_dev_us-east-1}"
has already performed the expansion you wanted and gotten you the result you needed. So you don't want the \${...} bit or eval. Just
USERNAME=DB_USER_dev_us-east-1
which you get from
USERNAME=${DBUSERNAME}
Unless I've missed something or your example isn't accurate.
As they have explained the problem, here is what you can do
$> more a.sh
ENV=dev
REGION=us-east-1
DBUSERNAME=DB_USER_${ENV}_$REGION
DBPASSWORD=DB_PASS_${ENV}_$REGION
eval "USERNAME=${DBUSERNAME}"
eval "PASSWORD=${DBPASSWORD}"
echo USERNAME=$USERNAME
echo PASSWORD=$PASSWORD
Results
$> ./a.sh
USERNAME=DB_USER_dev_us-east-1
PASSWORD=DB_PASS_dev_us-east-1

bash command expansion

The following bash command substitution does not work as I thought.
echo $TMUX_$(echo 1)
only prints 1 and I am expecting the value of the variable $TMUX_1.I also tried:
echo ${TMUX_$(echo 1)}
-bash: ${TMUXPWD_$(echo 1)}: bad substitution
Any suggestions ?
If I understand correctly what you're looking for, you're trying to programatically construct a variable name and then access the value of that variable. Doing this sort of thing normally requires an eval statement:
eval "echo \$TMUX_$(echo 1)"
Important features of this statement include the use of double-quotes, so that the $( ) gets properly interpreted as a command substitution, and the escaping of the first $ so that it doesn't get evaluated the first time through. Another way to achieve the same thing is
eval 'echo $TMUX_'"$(echo 1)"
where in this case I used two strings which automatically get concatenated. The first is single-quoted so that it's not evaluated at first.
There is one exception to the eval requirement: Bash has a method of indirect referencing, ${!name}, for when you want to use the contents of a variable as a variable name. You could use this as follows:
tmux_var = "TMUX_$(echo 1)"
echo ${!tmux_var}
I'm not sure if there's a way to do it in one statement, though, since you have to have a named variable for this to work.
P.S. I'm assuming that echo 1 is just a stand-in for some more complicated command ;-)
Are you looking for arrays? Bash has them. There are a number of ways to create and use arrays in bash, the section of the bash manpage on arrays is highly recommended. Here is a sample of code:
TMUX=( "zero", "one", "two" )
echo ${TMUX[2]}
The result in this case is, of course, two.
Here are a few short lines from the bash manpage:
Bash provides one-dimensional indexed and associative array variables. Any variable may be
used as an indexed array; the declare builtin will explicitly declare an array. There is
no maximum limit on the size of an array, nor any requirement that members be indexed or
assigned contiguously. Indexed arrays are referenced using integers (including arithmetic
expressions) and are zero-based; associative arrays are referenced using arbitrary
strings.
An indexed array is created automatically if any variable is assigned to using the syntax
name[subscript]=value. The subscript is treated as an arithmetic expression that must
evaluate to a number greater than or equal to zero. To explicitly declare an indexed
array, use declare -a name (see SHELL BUILTIN COMMANDS below). declare -a name[subscript]
is also accepted; the subscript is ignored.
This works (tested):
eval echo \$TMUX_`echo 1`
Probably not very clear though. Pretty sure any solutions will require backticks around the echo to get that to work.

what the differences between $var and ${var}

i see some code like this
SERIAL_NO="121"
if [ -z "${SERIAL_NO}" ]; then
echo -e "ERROR: serial number of the device is not provided!"
exit 1
else
here it use ${SERIAL_NO} to got the SERIAL_NO variable.
i want know what the difference between $var and ${var} and why use ${var} here.
thanks
There actually is a minor difference performance wise. The reason there is a performance difference is because the braces enable the Parameter Expansion (PE) parser. Without the braces the shell knows that no parameter expansion will be performed (as the braces are mandatory for PE). The only reason to use the braces around a variable name when you don't want to perform a PE is to disambiguate the variable name from other text such as var="foo"; echo ${var}name will output fooname. Without the braces the shell will try to expand the variable named $varname which doesn't exist.
There is no difference, if no alphanumeric characters follow. The braces in your example are unneeded.
"$foo42" is the contents of $foo42. "${foo}42" is the contents of $foo followed by "42".
The braces are often used to prevent shell expansion and allow back-to-back variables. However in this case it may not be strictly needed.

Resources