What is the meaning of the ${0##...} syntax with variable, braces and hash character in bash?
BashVariablesSyntaxCurly BracesBash Problem Overview
I just saw some code in bash that I didn't quite understand. Being the newbie bash scripter, I'm not sure what's going on.
echo ${0##/*}
echo ${0}
I don't really see a difference in output in these two commands (prints the script name). Is that #
just a comment? And what's with the /*
. If it is a comment, how come it doesn't interfere with the closing }
brace?
Can anyone give me some insight into this syntax?
Bash Solutions
Solution 1 - Bash
See the section on Substring removal in the Advanced Bash-Scripting Guide‡:
> ${string#substring}
> Deletes shortest match of substring
from front of $string
.
>
> ${string##substring}
>
> Deletes longest match of substring
from front of $string
.
The substring may include a wildcard *
, matching everything. The expression ${0##/*}
prints the value of $0
unless it starts with a forward slash, in which case it prints nothing.
‡ The guide, as of 3/7/2019, mistakenly claims that the match is of $substring
, as if substring
was the name of a variable. It's not: substring
is just a pattern.
Solution 2 - Bash
Linux tip: Bash parameters and parameter expansions
${PARAMETER##WORD} Results in removal of the longest matching pattern from the beginning rather than the shortest.
for example
[ian@pinguino ~]$ x="a1 b1 c2 d2"
[ian@pinguino ~]$ echo ${x#*1}
b1 c2 d2
[ian@pinguino ~]$ echo ${x##*1}
c2 d2
[ian@pinguino ~]$ echo ${x%1*}
a1 b
[ian@pinguino ~]$ echo ${x%%1*}
a
[ian@pinguino ~]$ echo ${x/1/3}
a3 b1 c2 d2
[ian@pinguino ~]$ echo ${x//1/3}
a3 b3 c2 d2
[ian@pinguino ~]$ echo ${x//?1/z3}
z3 z3 c2 d2
Solution 3 - Bash
I can't believe I'm answering a teen-aged question, but I think the existing answers (while certainly accurate) miss the practical thrust of the OP's question.
The OP asked about:
echo ${0##/*}
My guess is that what they really saw in the code was:
echo ${0##*/}
The latter essentially means "delete everything up to, and including, the last slash (if any)". So it's a concise way of getting the name of the script without the path, regardless of how the script was called. It's equivalent* to
basename "$0"
but is arguably handier (and more efficient) if you're using it as a variable rather than just printing it to the console. (OTOH basename is more portable, whereas the parameter expansion is a bashism.)
* More or less. There are edge cases (such as file names that start with a space) where they don't output exactly the same thing.
Solution 4 - Bash
Did you mean ##/*
, or ##*/
?
##/*
${0##/*}
is a bit unusual - it will strip off the prefix /...
from the start of $0
.
It's an all-or-nothing operation: If $0
starts with a slash (e.g. /home/bob/myscript.sh
), then it will strip everything and return an empty string. Otherwise (e.g. ./myscript.sh
) it will strip nothing and return the the whole of $0
.
(The double ##
indicates that it should strip the longest match; a single #
would only strip the first character, if it's a slash.)
One might use it to detect if a script is called from an absolute path or not.
##*/
${0##*/}
is more common - it will will strip off the prefix .../
from the
start of $0
.
e.g. if $0
is /home/bob/myscript.sh
, it will return myscript.sh
.
The ##
again indicates that it should strip the longest match, so it will strip all slashes (.../.../
). (As opposed to a #
, which will strip the first slash only, e.g. /home/bob/myscript.sh
-> home/bob/myscript.sh
)