Bash variable substitution vs dirname and basename
The external commands make some logical corrections. Check the result of the next script:
doit() {
str=$1
echo -e "string $str"
cmd=basename
[[ "${str##*/}" == "$($cmd $str)" ]] && echo "$cmd same: ${str##*/}" || echo -e "$cmd different \${str##*/}\t>${str##*/}<\tvs command:\t>$($cmd $str)<"
cmd=dirname
[[ "${str%/*}" == "$($cmd $str)" ]] && echo "$cmd same: ${str%/*}" || echo -e "$cmd different \${str%/*}\t>${str%/*}<\tvs command:\t>$($cmd $str)<"
echo
}
doit /aaa/bbb/
doit /
doit /aaa
doit aaa
doit aaa/
doit aaa/xxx
with the result
string /aaa/bbb/
basename different ${str##*/} >< vs command: >bbb<
dirname different ${str%/*} >/aaa/bbb< vs command: >/aaa<
string /
basename different ${str##*/} >< vs command: >/<
dirname different ${str%/*} >< vs command: >/<
string /aaa
basename same: aaa
dirname different ${str%/*} >< vs command: >/<
string aaa
basename same: aaa
dirname different ${str%/*} >aaa< vs command: >.<
string aaa/
basename different ${str##*/} >< vs command: >aaa<
dirname different ${str%/*} >aaa< vs command: >.<
string aaa/xxx
basename same: xxx
dirname same: aaa
One of most interesting results is the $(dirname "aaa")
. The external command dirname
correctly returns .
but the variable expansion ${str%/*}
returns the incorrect value aaa
.
Alternative presentation
Script:
doit() {
strings=( "[[$1]]"
"[[$(basename "$1")]]"
"[[${1##*/}]]"
"[[$(dirname "$1")]]"
"[[${1%/*}]]" )
printf "%-15s %-15s %-15s %-15s %-15s\n" "${strings[@]}"
}
printf "%-15s %-15s %-15s %-15s %-15s\n" \
'file' 'basename $file' '${file##*/}' 'dirname $file' '${file%/*}'
doit /aaa/bbb/
doit /
doit /aaa
doit aaa
doit aaa/
doit aaa/xxx
doit aaa//
Output:
file basename $file ${file##*/} dirname $file ${file%/*}
[[/aaa/bbb/]] [[bbb]] [[]] [[/aaa]] [[/aaa/bbb]]
[[/]] [[/]] [[]] [[/]] [[]]
[[/aaa]] [[aaa]] [[aaa]] [[/]] [[]]
[[aaa]] [[aaa]] [[aaa]] [[.]] [[aaa]]
[[aaa/]] [[aaa]] [[]] [[.]] [[aaa]]
[[aaa/xxx]] [[xxx]] [[xxx]] [[aaa]] [[aaa]]
[[aaa//]] [[aaa]] [[]] [[.]] [[aaa/]]
dirname
outputs.
if its parameter doesn't contain a slash/
, so emulatingdirname
with parameter substitution does not yield the same results depending on the input.basename
takes a suffix as second parameter which will also remove this component from the filename. You can emulate this as well using parameter substitution but since you cannot do both at once it is not as brief as when usingbasename
.Using either
dirname
orbasename
require a subshell since they are not shell builtins, so the parameter substitution will be faster, especially when calling them in a loop (as you have shown).I have seen
basename
in different locations on different systems (/usr/bin
,/bin
) so if you have to use absolute paths in your script for some reason it might break since it cannot find the executable.
So, yes, there are some things to consider and depending on situation and input I use both methods.
EDIT: Both dirname
and basename
are actually available as bash
loadable builtin
s under examples/loadables
in the source tree and can be enabled (once compiled) using
enable -f /path/to/dirname dirname
enable -f /path/to/basename basename