I can use the external command realpath
to get the absolute path of a file:
realpath tmp/toto
returns
/home/john/tmp/toto
Can I use a bash
built-in to get the same effect?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityThe bash
shell does not have a built-in way to explicitly get an absolute path given a relative pathname.
In the zsh
shell, one could do
pathname=../../home/kk/.zshenv
print -r -- $pathname:P
and get /home/kk/.zshenv
back (note that it also resolves all symbolic links wherever possible is a similar way as realpath()
would).
In bash
, you could, for a pathname designating a non-directory and assuming you have search access to its parent directory, use
$ pathname=../../home/kk/.zshenv
$ ( OLDPWD=- CDPATH= cd -P -- "${pathname%/*}" && printf '%s/%s\n' "$PWD" "${pathname##*/}" )
/home/kk/.zshenv
That is, temporarily (in a sub-shell) cd
to the directory holding the file, and then construct the absolute pathname using the value $PWD
and the filename component of the pathname. The -P
is needed to avoid cd
's special treatment of ..
components. It has the side effect of resolving symlinks components in the directory itself. If the file itself is a symlink however, it won't be resolved (that's a difference from zsh
's :P
modifier).
We set OLDPWD
to -
to work around the fact that cd -P -- -
does a chdir($OLDPWD)
instead of chdir("-")
(that trick works in bash
but not all other sh
implementations).
For a directory path it's easier:
$ pathname=../../home/kk
$ ( OLDPWD=- CDPATH= cd -P -- "$pathname" && pwd )
/home/kk
This could obviously be put into a shell function for convenience:
myrealpath () (
if [[ -d $1 ]]; then
OLDPWD=- CDPATH= cd -P -- "$1" && pwd
else
OLDPWD=- CDPATH= cd -P -- "${1%/*}" && printf '%s/%s\n' "$PWD" "${1##*/}"
fi
)
(Note that this whole function is defined in a sub-shell to avoid changing the working directory for the calling shell.)
Testing this function:
$ myrealpath ../../home/kk
/home/kk
$ myrealpath ../../home/kk/.zshenv
/home/kk/.zshenv
Depending on the OS, using the function with no argument will return the absolute path of the current directory or give an error. In future versions of bash
, that's likely to be an error on every system, as cd ''
to fail will become a POSIX requirement.
(cd somepath && some-command)
is a very common way of running a command with a displaced working directory. It avoids having to cd
back and forth in complicated ways.
zsh
, that's be more like $pathname:P
, $pathname:a
, $pathname:A
. See the manual (info zsh Modifiers
) for the differences.
Dec 21, 2019 at 12:44
bash
, I have deleted the mentioning of :A
and will instead only mention :P
, and will not go down the path (!) of trying to explain more about that.
Bash comes with a realpath
loadable extension. Since bash-4.4 this should be installed by default¹, so you should be able to do:
BASH_LOADABLES_PATH=${BASH_LOADABLES_PATH:-/usr/local/lib/bash:/usr/lib/bash}
enable -f realpath realpath
realpath ..
help realpath
Once enabled, each loadable command becomes a "real" builtin with no additional fork/exec overhead, and with the ability to manipulate the bash environment (where offered, e.g. the mypid
loadable)..
The -f
option specifies the path to the loadable extension binary, if it's not in the expected place or your distro didn't set it correctly, you will need to set the variable BASH_LOADABLES_PATH
to the directory containing extensions (or, use -f
with the full path to the loadable). If it's set correctly, you won't need the first line above.
When writing portable scripts that prefer, but do not require such extensions, I will use a wrapper function anyway — along the lines of @Kusalananda's — so that it can fall back to an external (such as readlink
or realpath
from coreutils).
I suggest the following, which have a high probability of working on non-Linux OSes (including AIX). To compute an absolute path (i.e. a path starting at /
):
abspath() {
[[ $1 = /* ]] && printf "%s\n" "$1" || printf "%s\n" "$PWD/$1"
}
For a canonical path (i.e. one with all symlinks expanded):
canonical_path() {
perl -mCwd -le ' print Cwd::abs_path $ARGV[0] ' -- "$1"
}
When testing against other possible solutions (including a call to realpath
), you'll notice a significant difference depending on whether or not the last path component exists, and whether or not the last path component is a symlink.
Some systems possess "realpath" command, e.g. Ubuntu. You can try realpath ~/foo.txt
and it could give something like /home/user/foo.txt
, then you can use basedir
to cut off file name.
bash
builtin cmd for that ? Any specific reason that may enlighten us ?realpath
command available at all.realpath
, only in GNU coreutils since 2012, for example. And even if there were not, there is no requirement for a built-in command. An alternative external command would surely suffice. OpenBSD version 2.1 in 1997 was the origin ofreadlink -f
, for example. unix.stackexchange.com/a/24342/5132