Related
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
Say if I have:
MYPATH=../Library/NetworkUtil/Classes/Headers/network.h
then I want to construct another path AllHeaders/NetworkUtil/network.h
I actually need to get different components out fro the original path, is there a way to do it?
I found in:
Bash: remove first directory component from variable (path of file)
I can something like
${MYPATH#../Library}
to strip out the specified part, but that assumes I know the structure already, what if in my case I need the 3rd and last components in the original path?
Thanks
You can use bash arrays to access individual elements:
$ MYPATH=../Library/NetworkUtil/Classes/Headers/network.h
$ OLD="$IFS"
$ IFS='/' a=($MYPATH)
$ IFS="$OLD"
$ NEWPATH="AllHeaders/${a[2]}/${a[-1]}"
$ echo $NEWPATH
AllHeaders/NetworkUtil/network.h
ADDENDUM: For completeness, another way to make MYPATH into an array is to use bash's pattern substitution: a=(${MYPATH//\// }):
$ MYPATH=../Library/NetworkUtil/Classes/Headers/network.h
$ a=(${MYPATH//\// })
$ NEWPATH="AllHeaders/${a[2]}/${a[-1]}"
$ echo $NEWPATH
AllHeaders/NetworkUtil/network.h
This eliminates the need for messing with IFS but would break badly if MYPATH had spaces, tabs, or CR in it to begin with.
I want to write a Genetic Algorithm for bash based in the one posted here: http://father-natures.blogspot.mx/2013/04/implementing-genetic-algorithm-in-bash.html. I am quite inexpert in advance scripting and I don't get what VAR=${n:-m} stands for. My guess was that things like:
POOL_SIZE=${1:-6}
Make $1=-6, however when I check $1 it is empty and when I check $POOLSIZE I get 6.
libertad#engrane4:~$ echo "POOL_SIZE"
6
This is quite confusing for me. If I wanted the variable to be 6 I would write:
POOL_SIZE=6
Could you tell me what am I missing (what else is this assignation doing)?
Thank you,
It sets a default in case $1 is empty.
From 3.5.3 Shell Parameter Expansion in the Bash Reference Manual:
${parameter:-word}
If parameter is unset or null, the expansion of word is substituted.
Otherwise, the value of parameter is substituted.
Example
$ echo ${a:-"hello"}
hello
$ a="test"
$ echo ${a:-"hello"}
test
Based on your comment
Thanks, #fedorqui. The original variables were POOL_SIZE=${1:-6},
REPRO_CHANCE=${2:-30}, BEST_FITS=${3:-70}. Now I am wondering if
${POOL_SIZE:-6}, ${REPRO_CHANCE:-30} and ${BEST_FITS:-70} would be the
same and why is the numeration needed
If you have
POOL_SIZE=${1:-6}
REPRO_CHANCE=${2:-30}
BEST_FITS=${3:-70}
it is because POOL_SIZE, REPRO_CHANCE and BEST_FITS are supposed to contain the value of $1, $2 and $3. Any $n means the nth parameter, for example from a script. So if you have the following script:
$ cat a
#!/bin/bash
POOL_SIZE=${1:-6}
REPRO_CHANCE=${2:-30}
BEST_FITS=${3:-70}
echo "POOL_SIZE=$POOL_SIZE"
echo "REPRO_CHANCE=$REPRO_CHANCE"
echo "BEST_FITS=$BEST_FITS"
Then its execution with different amount of parameters would yield:
$ ./a
POOL_SIZE=6
REPRO_CHANCE=30
BEST_FITS=70
$ ./a 2 2 2
POOL_SIZE=2
REPRO_CHANCE=2
BEST_FITS=2
$ ./a 24 2
POOL_SIZE=24
REPRO_CHANCE=2
BEST_FITS=70
I hope it makes it clear.
Note also that ${var:-value} and ${var-value} are not the same: What is the difference between ${var:-word} and ${var-word}?.
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
To clarify, I am looking for a way to perform a global search and replace on the previous command used. ^word^replacement^ only seems to replace the first match.
Is there some set option that is eluding me?
Try this:
$ echo oneone
oneone
$ !!:gs/one/two/ # Repeats last command; substitutes 'one' --> 'two'.
twotwo
This solution uses Bash Substring Replacement:
$ SENTENCE="1 word, 2 words";echo "${SENTENCE//word/replacement}"
1 replacement, 2 replacements
Note the use of the double slashes denotes "global" string replacement.
This solution can be executed in one line.
Here's how to globally replace a string in a file named "myfile.txt":
$ sed -i -e "s/word/replacement/g" myfile.txt
Blending my answer here with John Feminella's you can do this if you want an alias:
$alias dothis='`history -p "!?monkey?:gs/jpg/png/"`'
$ls *.jpg
monkey.jpg
$dothis
monkey.png
The !! only does the previous command, while !?string? matches the most recent command containing "string".
A nasty way to get around this could be something like this:
Want to echo BAABAA rather than BLABLA by swapping L's for A's
$ echo "BLABLA"
BLABLA
$ `echo "!!" | sed 's/L/A/g'`
$(echo "echo "BLABLA" " | sed 's/L/A/g')
BAABAA
$
Unfortunately this technique doesn't seem to work in functions or aliases.
this question has many dupes and one elegant answer only appears in this answer of user #Mikel in unix se
fc -s pat=rep
this bash builtin is documented under the chapter 9.2 Bash History Builtins
In the second form, command is re-executed after each instance of pat
in the selected command is replaced by rep. command is interpreted the
same as first above.
A useful alias to use with the fc command is r='fc -s', so that typing
‘r cc’ runs the last command beginning with cc and typing ‘r’
re-executes the last command (see Aliases).
I test it on SUSE 10.1.
"^word^replacement^" doesn't work, while "^word^replacement" works well.
for a instance:
linux-geek:/home/Myworks # ls /etc/ld.so.conf
/etc/ld.so.conf
linux-geek:/home/Myworks # ^ls^cat
cat /etc/ld.so.conf
/usr/X11R6/lib/Xaw3d
/usr/X11R6/lib
/usr/i486-linux-libc5/lib=libc5
/usr/i386-suse-linux/lib
/usr/local/lib
/opt/kde3/lib
/opt/gnome/lib
include /etc/ld.so.conf.d/*.conf
linux-geek:/home/Myworks #