I run this command both by typing in Terminal and by executing file on CentOS 6.5 64 bits.
let t='.'; echo $t;
Can't believe that it yields such a wierd error:
-bash: let: t=.: syntax error: operand expected (error token is ".")
As far as I know, single-quoted strings should not be parsed. In fact, in the first place, what I wanted to do is:
let $target_path='./files/mp4';
Can anyone please explain this behavior and guide me to stop this wierd act?
Unless you want to evaluate the variable value as an arithmetical expression, you need to assign the variable value like this:
t='.'
let is for calculation of arithmetical expressions and . or ./files/mp4 produces a syntax error in that arithmetical expression. Check help let.
Here comes an example how let can be used:
a="10*2"
echo "$a" # Prints: 10*2
let a="10*2"
echo "$a" # Prints: 20
If you followed the discussion below you may have noticed that even for mathematical expressions let isn't the best choice. This is because you can use ARITHMETIC EXPANSION in that case which is defined by POSIX in opposite to let. Using ARITHMETIC EXPANSION the above example would look like this:
a=$((10*2))
echo "$a" # Prints: 20
Check this articles for further information:
let
arithmetic expressions
Using let here is not right.
You can do like this:
AMD$ t='.'
AMD$ echo $t
.
AMD$ t='./files/mp4'
AMD$ echo $t
./files/mp4
Related
Let's say I have these variables:
VAR_A=resultA
VAR_B=resultB
X=A
I want to get the value of VAR_A or VAR_B based on the value of X.
This is working and gives resultA:
VAR="VAR_$X"
RESULT=${!VAR}
My question is, is there a one-liner for this?
Because indirection expansion doesn't seem to work if it is not the wole name of the variable which is expanded. I tried:
RESULT=${!VAR_$X}
RESULT=${!"VAR_$X"}
...and a lot of other combinations, but it always write "bad substitution"...
There doesn't appear to be a shorter way when using the bash 2 notation of ${!var}.
For reference, the "new" bash 2 notation is value=${!string_name_var} and the "old" notation would be: eval value=\$$string_name_var.
You could just use the old notation to make it work like you wanted:
eval RESULT=\$"VAR_$X"
Reference: https://www.tldp.org/LDP/abs/html/bashver2.html#BASH2REF
You'd be better off using an associative array, at least on recent versions of Bash:
declare -A var=(
[A]=resultA
[B]=resultB
)
x=A
result="${var[$x]}"
echo "$result" # -> resultA
This should work.
RESULT=$(eval echo \$VAR_$X)
The \ in front of the $ makes it be treated literally.
What I have is this:
progname=${0%.*}
progname=${progname##*/}
Can this be nested (or not) into one line, i.e. a single expression?
I'm trying to strip the path and extension off of a script name so that only the base name is left. The above two lines work fine. My 'C' nature is simply driving me to obfuscate these even more.
Bash supports indirect expansion:
$ FOO_BAR="foobar"
$ foo=FOO
$ foobar=${foo}_BAR
$ echo ${foobar}
FOO_BAR
$ echo ${!foobar}
foobar
This should support the nesting you are looking for.
If by nest, you mean something like this:
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
echo ${${HELLO}WORLD}
Then no, you can't nest ${var} expressions. The bash syntax expander won't understand it.
However, if I understand your problem right, you might look at using the basename command - it strips the path from a given filename, and if given the extension, will strip that also. For example, running basename /some/path/to/script.sh .sh will return script.
The following option has worked for me:
NAME="par1-par2-par3"
echo $(TMP=${NAME%-*};echo ${TMP##*-})
Output is:
par2
An old thread but perhaps the answer is the use of Indirection:${!PARAMETER}
For e.g., consider the following lines:
H="abc"
PARAM="H"
echo ${!PARAM} #gives abc
This nesting does not appear to be possible in bash, but it works in zsh:
progname=${${0%.*}##*/}
Expressions like ${${a}} do not work. To work around it, you can use eval:
b=value
a=b
eval aval=\$$a
echo $aval
Output is
value
Actually it is possible to create nested variables in bash, using two steps.
Here is a test script based upon the post by Tim, using the idea suggested by user1956358.
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
# This command does not work properly in bash
echo ${${HELLO}WORLD}
# However, a two-step process does work
export TEMP=${HELLO}WORLD
echo ${!TEMP}
The output is:
Hello, world!
There are lots of neat tricks explained by running 'info bash' from the command line, then searching for 'Shell Parameter Expansion'. I've been reading a few myself today, just lost about 20 minutes of my day, but my scripts are going to get a lot better...
Update: After more reading I suggest this alternative per your initial question.
progname=${0##*/}
It returns
bash
There is a 1 line solution to the OP's original question, the basename of a script with the file extension stripped:
progname=$(tmp=${0%.*} ; echo ${tmp##*/})
Here's another, but, using a cheat for basename:
progname=$(basename ${0%.*})
Other answers have wandered away from the OP's original question and focused on whether it's possible to just expand the result of expressions with ${!var} but came across the limitation that var must explicitly match an variable name. Having said that, there's nothing stopping you having a 1-liner answer if you chain the expressions together with a semicolon.
ANIMAL=CAT
BABYCAT=KITTEN
tmp=BABY${ANIMAL} ; ANSWER=${!tmp} # ANSWER=KITTEN
If you want to make this appear like a single statement, you can nest it in a subshell, i.e.
ANSWER=$( tmp=BABY${ANIMAL) ; echo ${!tmp} ) # ANSWER=KITTEN
An interesting usage is indirection works on arguments of a bash function. Then, you can nest your bash function calls to achieve multilevel nested indirection because we are allowed to do nested commands:
Here's a demonstration of indirection of an expression:
deref() { echo ${!1} ; }
ANIMAL=CAT
BABYCAT=KITTEN
deref BABY${ANIMAL} # Outputs: KITTEN
Here's a demonstration of multi level indirection thru nested commands:
deref() { echo ${!1} ; }
export AA=BB
export BB=CC
export CC=Hiya
deref AA # Outputs: BB
deref $(deref AA) # Outputs: CC
deref $(deref $(deref AA)) # Outputs: Hiya
As there is already a lot of answer there, I just want to present two different ways for doing both: nesting parameter expansion and variable name manipulation. (So you will find four different answer there:).
Parameter expansion not really nested, but done in one line:
Without semicolon (;) nor newline:
progname=${0%.*} progname=${progname##*/}
Another way: you could use a fork to basename
progname=$(basename ${0%.*})
This will make the job.
About concatenating variable name
If you want to construct varname, you could
use indirect expansion
foobar="baz"
varname="foo"
varname+="bar"
echo ${!varname}
baz
or use nameref
foobar="baz"
bar="foo"
declare -n reffoobar=${bar}bar
echo $reffoobar
baz
I know this is an ancient thread, but here are my 2 cents.
Here's an (admittedly kludgy) bash function which allows for the required functionality:
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
Here's a short test script:
#!/bin/bash
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
FOO=12
BAR=34
ABC_VAR=FOO
DEF_VAR=BAR
for a in ABC DEF; do
echo $a = $(read_var $(read_var ${a}_VAR))
done
The output is, as expected:
ABC = 12
DEF = 34
It will work if you follow the bellow shown way of taking on intermediate step :
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
varname=${HELLO}WORLD
echo ${!varname}
The basename bultin could help with this, since you're specifically splitting on / in one part:
user#host# var=/path/to/file.extension
user#host# basename ${var%%.*}
file
user#host#
It's not really faster than the two line variant, but it is just one line using built-in functionality. Or, use zsh/ksh which can do the pattern nesting thing. :)
Though this is a very old thread, this device is ideal for either directly or randomly selecting a file/directory for processing (playing tunes, picking a film to watch or book to read, etc).
In bash I believe it is generally true that you cannot directly nest any two expansions of the same type, but if you can separate them with some different kind of expansion, it can be done.
e=($(find . -maxdepth 1 -type d))
c=${2:-${e[$((RANDOM%${#e[#]}))]}}
Explanation: e is an array of directory names, c the selected directory, either named explicitly as $2,
${2:-...}
where ... is the alternative random selection given by
${e[$((RANDOM%${#e[#]}))]}
where the
$((RANDOM%...))
number generated by bash is divided by the number of items in array e, given by
${#e[#]}
yielding the remainder (from the % operator) that becomes the index to array e
${e[...]}
Thus you have four nested expansions.
If the motivation is to "obfuscate" (I would say streamline) array processing in the spirit of Python's "comprehensions", create a helper function that performs the operations in sequence.
function fixupnames()
{
pre=$1 ; suf=$2 ; shift ; shift ; args=($#)
args=(${args[#]/#/${pre}-})
args=(${args[#]/%/-${suf}})
echo ${args[#]}
}
You can use the result with a nice one-liner.
$ echo $(fixupnames a b abc def ghi)
a-abc-b a-def-b a-ghi-b
eval will allow you to do what you are wanting:
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
eval echo "\${${HELLO}WORLD}"
Output: Hello, world
Suppose I have the following code
for n in {50..300};
do
(( a = 0.3*$n))
#do something
echo $n
echo $a
done
when I run the code, I received an error, it says
((: a = 0.3*50: syntax error: invalid arithmetic operator (error token is ".3*50")
I know it must be because 0.3 or any decimal number isn't recognizable or perhaps due to some formatting issues, as I previously tried ((a = $n / 2)) which worked fine, much appreciated if anyone can give me a hint.
While this is quite a trivial syntax issue, using shellcheck.net to debug such errors would have been much efficient. Your error line needs to be something like
a=$(echo "0.3*$n" | bc ) # 'echo' to print an arithmetic expression
# feeding it to 'bc' for the actual computation.
I'm using bc to do a series of calculation.
I'm calling it though a bash script that first of all puts all the expressions to be calculated in a single variable, the passes them to bc to calculate the results.
The script is something like that:
#!/bin/bash
....
list=""
for frml in `cat $frmlList`
do
value=`echo $frml`
list="$list;$value"
done
echo "scale=3;$list"| bc
the frmlList variable contains a list of expressions that are the output of another program, for simplicity i don't mention every operation, but on its contents are done some sed operations before to assign it to the "value" variable.
In the end, the "list" variable contains a list of expressions separated by semicolon that bc understands.
Now what happens is that in my formula list, sometimes happens that there is a division by 0.
When it happens bc stops its computation giving a "Runtime Error: divide by zero".
I would bc to not end its work on that error, but to skip it and continue with the next formula evaluation.
Is possible to achieve something like that?
The same thing happens in a simpler situation:
echo "scale=2;1+1;1/0;2+2;" | bc
the output is
2
Runtime error (func=(main), adr=17): Divide by zero
I would like to have something like
2
Runtime error (func=(main), adr=17): Divide by zero
4
Thank you in advance :)
Ok, in the end i found a workaround that does the trick quite well.
The idea is to parallelize the execution of bc using subshells, this way if an evaluation fails the other can be still done.
In my script i did something like this:
#!/bin/bash
i=0
for frml in `cat $frmlList`
do
i=$((i+1))
(echo "scale=3;$value"| bc -l extensions.bc > "file_$i.tmp") &
if (( $i % 10 == 0 )); then
wait;
fi # Limit to 10 concurrent subshells.
done
#do something with the written files
I know no simple way to do this. If the expressions are independent, you can try to run them all in bc. If that fails, feed them to bc one by one, skipping the broken ones.
If expressions depend on each other, then you probably need something more powerful than bc. Or you can try to append expression after expression to an input file. If bc fails, remove the last one (maybe restore the file from a backup) and try with the next one.
What I have is this:
progname=${0%.*}
progname=${progname##*/}
Can this be nested (or not) into one line, i.e. a single expression?
I'm trying to strip the path and extension off of a script name so that only the base name is left. The above two lines work fine. My 'C' nature is simply driving me to obfuscate these even more.
Bash supports indirect expansion:
$ FOO_BAR="foobar"
$ foo=FOO
$ foobar=${foo}_BAR
$ echo ${foobar}
FOO_BAR
$ echo ${!foobar}
foobar
This should support the nesting you are looking for.
If by nest, you mean something like this:
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
echo ${${HELLO}WORLD}
Then no, you can't nest ${var} expressions. The bash syntax expander won't understand it.
However, if I understand your problem right, you might look at using the basename command - it strips the path from a given filename, and if given the extension, will strip that also. For example, running basename /some/path/to/script.sh .sh will return script.
The following option has worked for me:
NAME="par1-par2-par3"
echo $(TMP=${NAME%-*};echo ${TMP##*-})
Output is:
par2
An old thread but perhaps the answer is the use of Indirection:${!PARAMETER}
For e.g., consider the following lines:
H="abc"
PARAM="H"
echo ${!PARAM} #gives abc
This nesting does not appear to be possible in bash, but it works in zsh:
progname=${${0%.*}##*/}
Expressions like ${${a}} do not work. To work around it, you can use eval:
b=value
a=b
eval aval=\$$a
echo $aval
Output is
value
Actually it is possible to create nested variables in bash, using two steps.
Here is a test script based upon the post by Tim, using the idea suggested by user1956358.
#!/bin/bash
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
# This command does not work properly in bash
echo ${${HELLO}WORLD}
# However, a two-step process does work
export TEMP=${HELLO}WORLD
echo ${!TEMP}
The output is:
Hello, world!
There are lots of neat tricks explained by running 'info bash' from the command line, then searching for 'Shell Parameter Expansion'. I've been reading a few myself today, just lost about 20 minutes of my day, but my scripts are going to get a lot better...
Update: After more reading I suggest this alternative per your initial question.
progname=${0##*/}
It returns
bash
There is a 1 line solution to the OP's original question, the basename of a script with the file extension stripped:
progname=$(tmp=${0%.*} ; echo ${tmp##*/})
Here's another, but, using a cheat for basename:
progname=$(basename ${0%.*})
Other answers have wandered away from the OP's original question and focused on whether it's possible to just expand the result of expressions with ${!var} but came across the limitation that var must explicitly match an variable name. Having said that, there's nothing stopping you having a 1-liner answer if you chain the expressions together with a semicolon.
ANIMAL=CAT
BABYCAT=KITTEN
tmp=BABY${ANIMAL} ; ANSWER=${!tmp} # ANSWER=KITTEN
If you want to make this appear like a single statement, you can nest it in a subshell, i.e.
ANSWER=$( tmp=BABY${ANIMAL) ; echo ${!tmp} ) # ANSWER=KITTEN
An interesting usage is indirection works on arguments of a bash function. Then, you can nest your bash function calls to achieve multilevel nested indirection because we are allowed to do nested commands:
Here's a demonstration of indirection of an expression:
deref() { echo ${!1} ; }
ANIMAL=CAT
BABYCAT=KITTEN
deref BABY${ANIMAL} # Outputs: KITTEN
Here's a demonstration of multi level indirection thru nested commands:
deref() { echo ${!1} ; }
export AA=BB
export BB=CC
export CC=Hiya
deref AA # Outputs: BB
deref $(deref AA) # Outputs: CC
deref $(deref $(deref AA)) # Outputs: Hiya
As there is already a lot of answer there, I just want to present two different ways for doing both: nesting parameter expansion and variable name manipulation. (So you will find four different answer there:).
Parameter expansion not really nested, but done in one line:
Without semicolon (;) nor newline:
progname=${0%.*} progname=${progname##*/}
Another way: you could use a fork to basename
progname=$(basename ${0%.*})
This will make the job.
About concatenating variable name
If you want to construct varname, you could
use indirect expansion
foobar="baz"
varname="foo"
varname+="bar"
echo ${!varname}
baz
or use nameref
foobar="baz"
bar="foo"
declare -n reffoobar=${bar}bar
echo $reffoobar
baz
I know this is an ancient thread, but here are my 2 cents.
Here's an (admittedly kludgy) bash function which allows for the required functionality:
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
Here's a short test script:
#!/bin/bash
read_var() {
set | grep ^$1\\b | sed s/^$1=//
}
FOO=12
BAR=34
ABC_VAR=FOO
DEF_VAR=BAR
for a in ABC DEF; do
echo $a = $(read_var $(read_var ${a}_VAR))
done
The output is, as expected:
ABC = 12
DEF = 34
It will work if you follow the bellow shown way of taking on intermediate step :
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
varname=${HELLO}WORLD
echo ${!varname}
The basename bultin could help with this, since you're specifically splitting on / in one part:
user#host# var=/path/to/file.extension
user#host# basename ${var%%.*}
file
user#host#
It's not really faster than the two line variant, but it is just one line using built-in functionality. Or, use zsh/ksh which can do the pattern nesting thing. :)
Though this is a very old thread, this device is ideal for either directly or randomly selecting a file/directory for processing (playing tunes, picking a film to watch or book to read, etc).
In bash I believe it is generally true that you cannot directly nest any two expansions of the same type, but if you can separate them with some different kind of expansion, it can be done.
e=($(find . -maxdepth 1 -type d))
c=${2:-${e[$((RANDOM%${#e[#]}))]}}
Explanation: e is an array of directory names, c the selected directory, either named explicitly as $2,
${2:-...}
where ... is the alternative random selection given by
${e[$((RANDOM%${#e[#]}))]}
where the
$((RANDOM%...))
number generated by bash is divided by the number of items in array e, given by
${#e[#]}
yielding the remainder (from the % operator) that becomes the index to array e
${e[...]}
Thus you have four nested expansions.
If the motivation is to "obfuscate" (I would say streamline) array processing in the spirit of Python's "comprehensions", create a helper function that performs the operations in sequence.
function fixupnames()
{
pre=$1 ; suf=$2 ; shift ; shift ; args=($#)
args=(${args[#]/#/${pre}-})
args=(${args[#]/%/-${suf}})
echo ${args[#]}
}
You can use the result with a nice one-liner.
$ echo $(fixupnames a b abc def ghi)
a-abc-b a-def-b a-ghi-b
eval will allow you to do what you are wanting:
export HELLO="HELLO"
export HELLOWORLD="Hello, world!"
eval echo "\${${HELLO}WORLD}"
Output: Hello, world