How to access a variable that its name is made up by various strings in Shell Script? - shell

So, this is similar to passing parameter by reference. I want to access a variable (echo) that its name is a combined string from different strings. A simple example would be the following:
A1=999
n="1"
B="A$n"
What I want is that when I do echo $B, it would return 999. Please let me know if further explanation is required. Thanks.

You are looking for indirection
echo ${!B}
From the bash manual
${!prefix*}
${!prefix#}
Expands to the names of variables whose names begin with prefix,
separated by the first character of the IFS special variable.

You could also do this
eval echo "$"$B
But Kevin's answer is definitely better.

Related

How to make a string, not a value of a variable but a new variable? [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed last year.
I have these variables:
var1=ab
var2=cd
result=${var1}-text-${var2}
ab-text-cd=bingo
I have:
$ echo $result
ab-text-cd
I would like to have:
$ echo $result
bingo
Is it possible and how?
More info:
Var1 and var2 are arguments given to script.
Thanks to #Léa Gris.
I didn't know about "indirect parameter expansion".
"If the first character of PARAMETER is an exclamation point, Bash uses the value of the variable formed from the rest of PARAMETER as the name of the variable."
Solution :
result2=$(echo ${!result1})
You can use eval to achieve this. Be warned though, that using eval is almost always a bad idea, as it has glaring security issues (rooted in its design -- it is meant to execute everything passed to it) and even apart from that, all kinds of things might go wrong when a variable has an unexpected value.
result=${var1}-text-${var2}
eval ${var1}'_text_'${var2}=bingo
echo $ab_text_cd
Also, environment variables cannot have dashes (-) as part of the variable name, so I replaced them by underscores (_) for the example.

Why can substring variable expansion reference a variable without a dollar sign?

In bash, to get the first 4 characters of a variable, you can do:
variable='this is a variable'
echo ${variable:0:4}
Instead of hard-coding the length, you can reference a variable like this:
length=4
echo ${variable:0:$length}
However, it seems that you can leave off the $ off length as well:
echo ${variable:0:length}
It does not make sense to me that you should be able to do this because I always thought that to use/evaluate a variable, you have to prefix it with $.
In other languages, I would expect the text after each : to be a number or an expression that evaluates to a number. And in bash, length wouldn't evaluate to anything, but $length would.
This is confusing. Could someone help me understand what is going on here?
In general is correct to use the "$" symbol to expand a variable, but in some cases the bash auto-expands variable. For example in context like arithmetics or indirect expansion
(see Shell expansion to more detailed information).
However your case is a simple arithmetic context expansion.

concatenate variables in a bash script

Hi I would like to set a variable by concatenating two other variables.
Example
A=1
B=2
12=C
echo $A$B
desired result being C
however the answer I get is always 12
Is it possible?
UPDATED
Example
A=X
B=Y
D=$A$B
xy=test
echo $D
desired result being "test"
It looks like you want indirect variable references.
BASH allows you to expand a parameter indirectly -- that is, one variable may contain the name of another variable:
# Bash
realvariable=contents
ref=realvariable
echo "${!ref}" # prints the contents of the real variable
But as Pieter21 indicates in his comment 12 is not a valid variable name.
Since 12 is not a valid variable name, here's an example with string variables:
> a='hello'
> b='world'
> declare my_$a_$b='my string'
> echo $my_hello_world
my string
What you are trying to do is (almost) called indirection: http://wiki.bash-hackers.org/syntax/pe#indirection
...I did some quick tests, but it does not seem logical to do this without a third variable - you cannot do concatenated indirection directly as the variables/parts being concatenated do not evaluate to the result on their own - you would have to do another evaluation. I think concatenating them first might be the easiest. That said, there is a chance you could rethink what you're doing.
Oh, and you cannot use numbers (alone or as the starting character) for variable names.
Here we go:
cake="cheese"
var1="ca"
var2="ke"
# this does not work as the indirection sees "ca" and "ke", not "cake". No output.
echo ${!var1}${!var2}
# there might be some other ways of tricking it to do this, but they don't look to sensible as indirection probably needs to work on a real variable.
# ...this works, though:
var3=${var1}${var2}
echo ${!var3}

Bash construct to extract the two values from commands like:"--option=false"?

Does anyone know the bash construct where you specify the delimeter and you get in two variables ( $# or $! or something like that I think ) the values?
For example:
--option=false should be specified with '=' delimeter and in one variable there will be the word 'option' whereas in the other variable the word 'false' is stored. Anyone know?
P.S.: No sed, awk or IFS solutions please. I am aware of them but I am requesting the specific bash construct :)
As found in the Bash man page, you can use Parameter Expansion to solve your problems.
# split first argument on equal sign (left=right)
left=${1%%=*}
right=${1#*=}
I believe the GNU getopt program is appropriate here (if available, of course).
It supports long options.

Bash bad substitution with subshell and substring

A contrived example... given
FOO="/foo/bar/baz"
this works (in bash)
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
this doesn't
BAZ=${$(basename $FOO):0:1} # result is bad substitution
My question is which rule causes this [subshell substitution] to evaluate incorrectly? And what is the correct way, if any, to do this in 1 hop?
First off, note that when you say this:
BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1} # result is BAZ="b"
the first bit in the construct for BAZ is BAR and not the value that you want to take the first character of. So even if bash allowed variable names to contain arbitrary characters your result in the second expression wouldn't be what you want.
However, as to the rule that's preventing this, allow me to quote from the bash man page:
DEFINITIONS
The following definitions are used throughout the rest of this docu‐
ment.
blank A space or tab.
word A sequence of characters considered as a single unit by the
shell. Also known as a token.
name A word consisting only of alphanumeric characters and under‐
scores, and beginning with an alphabetic character or an under‐
score. Also referred to as an identifier.
Then a bit later:
PARAMETERS
A parameter is an entity that stores values. It can be a name, a num‐
ber, or one of the special characters listed below under Special Param‐
eters. A variable is a parameter denoted by a name. A variable has a
value and zero or more attributes. Attributes are assigned using the
declare builtin command (see declare below in SHELL BUILTIN COMMANDS).
And later when it defines the syntax you're asking about:
${parameter:offset:length}
Substring Expansion. Expands to up to length characters of
parameter starting at the character specified by offset.
So the rules as articulated in the manpage say that the ${foo:x:y} construct must have a parameter as the first part, and that a parameter can only be a name, a number, or one of the few special parameter characters. $(basename $FOO) is not one of the allowed possibilities for a parameter.
As for a way to do this in one assignment, use a pipe to other commands as mentioned in other responses.
Modified forms of parameter substitution such as ${parameter#word} can only modify a parameter, not an arbitrary word.
In this case, you might pipe the output of basename to a dd command, like
BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)
(If you want a higher count, increase count and not bs, otherwise you may get fewer bytes than requested.)
In the general case, there is no way to do things like this in one assignment.
It fails because ${BAR:0:1} is a variable expansion. Bash expects to see a variable name after ${, not a value.
I'm not aware of a way to do it in a single expression.
As others have said, the first parameter of ${} needs to be a variable name. But you can use another subshell to approximate what you're trying to do.
Instead of:
BAZ=${$(basename $FOO):0:1} # result is bad substitution
Use:
BAZ=$(_TMP=$(basename $FOO); echo ${_TMP:0:1}) # this works
A contrived solution for your contrived example:
BAZ=$(expr $(basename $FOO) : '\(.\)')
as in
$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j
${string:0:1},string must be a variable name
for example:
FOO="/foo/bar/baz"
baz="foo"
BAZ=eval echo '${'"$(basename $FOO)"':0:1}'
echo $BAZ
the result is 'f'

Resources