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?
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?
The 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.
I'm aware of $pathname(:a) in zsh. That's exactly what I want to find equivalent in bash.
Using a sub-shell to get just an absolute path is a little overkill.
myrealpath is a nice try, but it does "cd", I still have to "cd" back to the original directory.
– Philippe Dec 21 '19 at 11:49(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.
– Kusalananda
Dec 21 '19 at 12:01
zsh
, that's be more like $pathname:P
, $pathname:a
, $pathname:A
. See the manual (info zsh Modifiers
) for the differences.
– Stéphane Chazelas
Dec 21 '19 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.
– Kusalananda
Dec 21 '19 at 13:54
realpath(){ zsh -c "realpath $@"; }
laziest
– Rakib Fiha
Dec 21 '19 at 14:04
zsh
shell does not have a built in realpath
utility, so your function would end up calling the external realpath
utility anyway, if it's available.
– Kusalananda
Dec 21 '19 at 14:07
realpath
was zsh bultin or something like that. Just saw, OP mentioned its an external command
– Rakib Fiha
Dec 21 '19 at 14:10
$pathname:P
as the P
modifier applied to parameter expansion as opposed to a (:P)
glob qualifier. The glob qualifier requires the file to exist.
– Stéphane Chazelas
Dec 21 '19 at 17:47
myrealpath
fails to produce the same results as realpath
if the last path component is a symlink. Also, if you just want an absolute path, I don't understand the need to use cd
. Just using $PWD
should be sufficient (it's a different story if you use cd -P
).
– jrw32982
Dec 27 '19 at 06:01
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 ? – Cbhihe Dec 21 '19 at 11:30realpath
command available at all. – Kusalananda Dec 21 '19 at 11:40realpath
, 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. https://unix.stackexchange.com/a/24342/5132 – JdeBP Dec 21 '19 at 14:15bash
will spawn a separate OS process for any subshell anyway, and unless your mythicalrealpath
builtin were specially designed to take a variable name (likeexport
,local
, etc), you would need a command substitution (= subshell -> subprocess) in order to get its output into a variable. – Dec 22 '19 at 05:41realpath
for now, to keep it simple. – Philippe Dec 22 '19 at 17:02