Non-trivial command-line fu | Follow @rtfmsh |
fullname() { echo $(cd $(dirname ${1}) && pwd -P)/$(basename ${1}); }
— -sh (@rtfmsh) February 8, 2013
Every now and then I need to identify the absolute path for a given filename. By this, I mean the actual path from the root of the file system, without any ./ or ../, and with all symbolic links within the pathname resolved. This command does precisely that.
The basename(1) part should be pretty obvious, but getting the actual physical path of the dirname(1) component requires us to cd(1) into the directory and ask pwd(1) for help. Neatly wrapped up into a function, we now can give this a spin:
$ fullname() { echo $(cd $(dirname ${1}) && pwd -P)/$(basename ${1}); }
$ fullname .
/home/jschauma/.
$ fullname ../../../../../../var/tmp/../run/../../bin/ls
/bin/ls
$
So far, so good. Now you might be tempted to replace $(basename ${1}) with ${1##*/} and $(dirname ${1}) with ${1%/*} to save the overhead of the subshells and invocations of commands. But it turns out that that actually works somewhat less reliably, especially when you have multiple trailing slashes:
$ fullname() { echo $(cd $(dirname ${1}) && pwd -P)/$(basename ${1}); }
$ fullname2() { echo $(cd ${1%/*} && pwd -P)/${1##*/}; }
$ fullname .
/home/jschauma/.
$ fullname2 .
/home/jschauma/.
$ fullname ../../../../../../var/tmp/../run/../../bin/ls
/bin/ls
$ fullname2 ../../../../../../var/tmp/../run/../../bin/ls
/bin/ls
$ fullname ../../../../../../var/tmp/../run/../../usr/bin////
/usr/bin
$ fullname2 ../../../../../../var/tmp/../run/../../usr/bin////
/usr/bin/
$
The last result is slightly different: a trailing slash is left in the second case. This makes a difference if you wish to use the resulting string to create new pathnames, and is, simply, incorrect.
You may also notice that even using dirname(1) and basename(1) this command has a bug:
$ fullname() { echo $(cd $(dirname ${1}) && pwd -P)/$(basename ${1}); }
$ fullname /bin
//bin
$
That is, whenever the dirname(1) component evaluates to the root of the filesystem, we end up with two starting slashes. I've not been able to find a reasonable solution to this (without the use of conditionals and/or variables. And so, the end result is thus a function with three subshells, executing three external commands and two shell builtins, proving that even the shortest programs tend to contain bugs.
2013-02-08