Question: is there a simple sh/bash/zsh/fish/... command to print the absolute path of whichever file I feed it?
Usage case: I'm in directory /a/b
and I'd like to print the full path to file c
on the command-line so that I can easily paste it into another program: /a/b/c
. Simple, yet a little program to do this could probably save me 5 or so seconds when it comes to handling long paths, which in the end adds up. So it surprises me that I can't find a standard utility to do this — is there really none?
Here's a sample implementation, abspath.py:
#!/usr/bin/python
# Author: Diggory Hardy <diggory.hardy@gmail.com>
# Licence: public domain
# Purpose: print the absolute path of all input paths
import sys
import os.path
if len(sys.argv)>1:
for i in range(1,len(sys.argv)):
print os.path.abspath( sys.argv[i] )
sys.exit(0)
else:
print >> sys.stderr, "Usage: ",sys.argv[0]," PATH."
sys.exit(1)
Try readlink
which will resolve symbolic links:
readlink -e /foo/bar/baz
-m
is the best option to use.
-f
option is also the one to use in OpenBSD
readlink
manpage: only the target of the symbolic link is printed. If the given argument is not a symbolic link, readlink will print nothing and exit with an error
, so readlink won't work for this purpose on OSX
#! /bin/sh
echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")"
a b
(two spaces between a and b, SO eats one of them).
function realpath { echo $(cd $(dirname $1); pwd)/$(basename $1); }
to make myself a realpath
command (currently accepted answer) on OS X. Thanks!
LOG=$(echo $(cd $(dirname $0); pwd)/$(basename $0 .sh).log)
Forget about readlink
and realpath
which may or may not be installed on your system.
Expanding on dogbane's answer above here it is expressed as a function:
#!/bin/bash
get_abs_filename() {
# $1 : relative filename
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}
you can then use it like this:
myabsfile=$(get_abs_filename "../../foo/bar/file.txt")
How and why does it work?
The solution exploits the fact that the Bash built-in pwd
command will print the absolute path of the current directory when invoked without arguments.
Why do I like this solution ?
It is portable and doesn't require neither readlink
or realpath
which often does not exist on a default install of a given Linux/Unix distro.
What if dir doesn't exist?
As given above the function will fail and print on stderr if the directory path given does not exist. This may not be what you want. You can expand the function to handle that situation:
#!/bin/bash
get_abs_filename() {
# $1 : relative filename
if [ -d "$(dirname "$1")" ]; then
echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
fi
}
Now it will return an empty string if one the parent dirs do not exist.
How do you handle trailing '..' or '.' in input ?
Well, it does give an absolute path in that case, but not a minimal one. It will look like:
/Users/bob/Documents/..
If you want to resolve the '..' you will need to make the script like:
get_abs_filename() {
# $1 : relative filename
filename=$1
parentdir=$(dirname "${filename}")
if [ -d "${filename}" ]; then
echo "$(cd "${filename}" && pwd)"
elif [ -d "${parentdir}" ]; then
echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
fi
}
realpath
comes from coreutils package, which also contains tools such as who
, touch
or cat
. It's quite standar set of GNU tools, so you can be quite sure that this is installed on almost any linux based machine.That said, you are right: there are 2 issues with it: i) you will likely miss it in default install on non GNU unix systems (like OpenBSD) ii) this tools was introduced in version 8.15 (so it's quite new, from 2012), so you will miss it on long term stable systems (like RHEL 6).
$ readlink -m FILE
/path/to/FILE
This is better than readlink -e FILE
or realpath
, because it works even if the file doesn't exist.
-m
option is not available on Mavericks.
This relative path to absolute path converter shell function
requires no utilities (just cd and pwd)
works for directories and files
handles .. and .
handles spaces in dir or filenames
requires that file or directory exists
returns nothing if nothing exists at the given path
handles absolute paths as input (passes them through essentially)
Code:
function abspath() {
# generate absolute path from relative path
# $1 : relative filename
# return : absolute path
if [ -d "$1" ]; then
# dir
(cd "$1"; pwd)
elif [ -f "$1" ]; then
# file
if [[ $1 = /* ]]; then
echo "$1"
elif [[ $1 == */* ]]; then
echo "$(cd "${1%/*}"; pwd)/${1##*/}"
else
echo "$(pwd)/$1"
fi
fi
}
Sample:
# assume inside /parent/cur
abspath file.txt => /parent/cur/file.txt
abspath . => /parent/cur
abspath .. => /parent
abspath ../dir/file.txt => /parent/dir/file.txt
abspath ../dir/../dir => /parent/dir # anything cd can handle
abspath doesnotexist => # empty result if file/dir does not exist
abspath /file.txt => /file.txt # handle absolute path input
Note: This is based on the answers from nolan6000 and bsingh, but fixes the file case.
I also understand that the original question was about an existing command line utility. But since this seems to be THE question on stackoverflow for that including shell scripts that want to have minimal dependencies, I put this script solution here, so I can find it later :)
$ abspath 'a b c/file.txt'
That's because the argument to cd
isn't quoted. To overcome this, instead of quoting the entire argument to echo
(is there a reason for these quotes?) quote only the path arguments. I.e. replace echo "$(cd ${1%/*}; pwd)/${1##*/}"
with echo $(cd "${1%/*}"; pwd)/${1##*/}
.
echo "$(cd "$1"; pwd)"
with (cd "$1"; pwd)
. There is no need for echo here.
( ... )
are still needed, so the cd
happens in a subshell, as we don't want to change the working directory for any callers of abspath
.
The find
command may help
find $PWD -name ex*
find $PWD -name example.log
Lists all the files in or below the current directory with names matching the pattern. You can simplify it if you will only get a few results (e.g. directory near bottom of tree containing few files), just
find $PWD
I use this on Solaris 10, which doesn't have the other utilities mentioned.
PWD
, you could simply use $PWD/$filename
.
Here's a zsh-only function that I like for its compactness. It uses the ‘A’ expansion modifier — see zshexpn(1).
realpath() { for f in "$@"; do echo ${f}(:A); done }
If you don't have readlink or realpath utilities than you can use following function which works in bash and zsh (not sure about the rest).
abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; }
This also works for nonexistent files (as does the python function os.path.abspath
).
Unfortunately abspath ./../somefile
doesn't get rid of the dots.
echo "$1"
with printf "%s\n" "$1"
(same with the second echo). echo
may interpret backslashes inside its arguments.
There is generally no such thing as the absolute path
to a file (this statement means that there may be more than one in general, hence the use of the definite article the is not appropriate). An absolute path
is any path that start from the root "/" and designates a file without ambiguity independently of the working directory.(see for example wikipedia).
A relative path
is a path that is to be interpreted starting from another directory. It may be the working directory if it is a relative path
being manipulated by an application (though not necessarily). When it is in a symbolic link in a directory, it is generally intended to be relative to that directory (though the user may have other uses in mind).
Hence an absolute path is just a path relative to the root directory.
A path (absolute or relative) may or may not contain symbolic links. If it does not, it is also somewhat impervious to changes in the linking structure, but this is not necessarily required or even desirable. Some people call canonical path
( or canonical file name
or resolved path
) an absolute path
in which all symbolic links have been resolved, i.e. have been replaced by a path to whetever they link to. The commands realpath
and readlink
both look for a canonical path, but only realpath
has an option for getting an absolute path without bothering to resolve symbolic links (along with several other options to get various kind of paths, absolute or relative to some directory).
This calls for several remarks:
symbolic links can only be resolved if whatever they are supposed to link to is already created, which is obviously not always the case. The commands realpath and readlink have options to account for that. a directory on a path can later become a symbolic link, which means that the path is no longer canonical. Hence the concept is time (or environment) dependent. even in the ideal case, when all symbolic links can be resolved, there may still be more than one canonical path to a file, for two reasons: the partition containing the file may have been mounted simultaneously (ro) on several mount points. there may be hard links to the file, meaning essentially the the file exists in several different directories.
Hence, even with the much more restrictive definition of canonical path
, there may be several canonical paths to a file. This also means that the qualifier canonical
is somewhat inadequate since it usually implies a notion of uniqueness.
This expands a brief discussion of the topic in an answer to another similar question at Bash: retrieve absolute path given relative
My conclusion is that realpath
is better designed and much more flexible than readlink
. The only use of readlink
that is not covered by realpath
is the call without option returning the value of a symbolic link.
realpath
vs readlink
or even about symbolic links are surplus to what I asked.
canonical
refers precisely to this uniqueness relative to a set of transformations. But, for example, /dev/cdrom is a perfectly good absolute path, even though it is actually a link to /dev/sr0. Both point to the same device. My original answer pointed to a relevant web article.
The simplest if you want to use only builtins is probably:
find `pwd` -name fileName
Only an extra two words to type, and this will work on all unix systems, as well as OSX.
-maxdepth 1
before -name
(supposing the file is in the current directory). Without that it will work with files in any subdirectory of pwd, but not with others.
find
won't find a file outside the directory tree underneath pwd
. E.g., if you try to find . -name ../existingFile
, it fails.
The dogbane
answer with the description what is coming on:
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
Explanation:
This script get relative path as argument "$1" Then we get dirname part of that path (you can pass either dir or file to this script): dirname "$1" Then we cd "$(dirname "$1") into this relative dir and get absolute path for it by running pwd shell command After that we append basename to absolute path: $(basename "$1") As final step we echo it
Answer with Homebrew
realpath
is the best answer, but if you don't have it installed, you must first run brew install coreutils
which will install coreutils with lots of awesome functions. Writing a custom function and exporting it is too much work and risk for error for something like this, here are two lines:
$ brew install coreutils
$ realpath your-file-name.json
The top answers in this question may be misleading in some cases. Imagine that the file, whose absolute path you want to find, is in the $PATH
variable:
# node is in $PATH variable
type -P node
# /home/user/.asdf/shims/node
cd /tmp
touch node
readlink -e node
# /tmp/node
readlink -m node
# /tmp/node
readlink -f node
# /tmp/node
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node
realpath node
# /tmp/node
realpath -e node
# /tmp/node
# Now let's say that for some reason node does not exist in current directory
rm node
readlink -e node
# <nothing printed>
readlink -m node
# /tmp/node # Note: /tmp/node does not exist, but is printed
readlink -f node
# /tmp/node # Note: /tmp/node does not exist, but is printed
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node # Note: /tmp/node does not exist, but is printed
realpath node
# /tmp/node # Note: /tmp/node does not exist, but is printed
realpath -e node
# realpath: node: No such file or directory
Based on the above I can conclude that: realpath -e
and readlink -e
can be used for finding the absolute path of a file, that we expect to exist in current directory, without result being affected by the $PATH
variable. The only difference is that realpath
outputs to stderr, but both will return error code if file is not found:
cd /tmp
rm node
realpath -e node ; echo $?
# realpath: node: No such file or directory
# 1
readlink -e node ; echo $?
# 1
Now in case you want the absolute path a of a file that exists in $PATH
, the following command would be suitable, independently on whether a file with same name exists in current dir.
type -P example.txt
# /path/to/example.txt
# Or if you want to follow links
readlink -e $(type -P example.txt)
# /originalpath/to/example.txt
# If the file you are looking for is an executable (and wrap again through `readlink -e` for following links )
which executablefile
# /opt/bin/executablefile
And a, fallback to $PATH
if missing, example:
cd /tmp
touch node
echo $(readlink -e node || type -P node)
# /tmp/node
rm node
echo $(readlink -e node || type -P node)
# /home/user/.asdf/shims/node
For directories dirname
gets tripped for ../
and returns ./
.
nolan6000's function can be modified to fix that:
get_abs_filename() {
# $1 : relative filename
if [ -d "${1%/*}" ]; then
echo "$(cd ${1%/*}; pwd)/${1##*/}"
fi
}
.profile
and hence available for interactive use.
$1
is a plain filename: get_abs_filename foo
-> nothing (or <current_dir>/foo/foo
if foo
is a directory).
This is not an answer to the question, but for those who does scripting:
echo `cd "$1" 2>/dev/null&&pwd||(cd "$(dirname "$1")";pwd|sed "s|/*\$|/${1##*/}|")`
it handles / .. ./ etc correctly. I also seems to work on OSX
I have placed the following script on my system & I call it as a bash alias for when I want to quickly grab the full path to a file in the current dir:
#!/bin/bash
/usr/bin/find "$PWD" -maxdepth 1 -mindepth 1 -name "$1"
I am not sure why, but, on OS X when called by a script "$PWD" expands to the absolute path. When the find command is called on the command line, it doesn't. But it does what I want... enjoy.
$PWD
always has the working directory's absolute path. Also, find
won't tolerate slashes, so you're not answering the question as asked. A quicker way to do what you're looking to do would simply be echo "$PWD/$1"
#! /bin/bash
file="$@"
realpath "$file" 2>/dev/null || eval realpath $(echo $file | sed 's/ /\\ /g')
This makes up for the shortcomings of realpath
, store it in a shell script fullpath
. You can now call:
$ cd && touch a\ a && rm A 2>/dev/null
$ fullpath "a a"
/home/user/a a
$ fullpath ~/a\ a
/home/user/a a
$ fullpath A
A: No such file or directory.
realpath "foo bar"
-> /home/myname/foo bar
. If you can't be bothered to quote command line arguments, that's not realpath
's fault.
An alternative to get the absolute path in Ruby:
realpath() {ruby -e "require 'Pathname'; puts Pathname.new('$1').realpath.to_s";}
Works with no arguments (current folder) and relative and absolute file or folder path as agument.
The answer of Alexander Klimetschek is okay if your script may insist on a bash or bash compatible shell being present. It won't work with a shell that is only POSIX conforming.
Also when the final file is a file in root, the output will be //file
, which is not technically incorrect (double /
are treated like single ones by the system) but it looks strange.
Here's a version that works with every POSIX conforming shell, all external tools it is using are also required by the POSIX standard, and it explicitly handles the root-file case:
#!/bin/sh
abspath ( ) {
if [ ! -e "$1" ]; then
return 1
fi
file=""
dir="$1"
if [ ! -d "$dir" ]; then
file=$(basename "$dir")
dir=$(dirname "$dir")
fi
case "$dir" in
/*) ;;
*) dir="$(pwd)/$dir"
esac
result=$(cd "$dir" && pwd)
if [ -n "$file" ]; then
case "$result" in
*/) ;;
*) result="$result/"
esac
result="$result$file"
fi
printf "%s\n" "$result"
}
abspath "$1"
Put that into a file and make it executable and you have a CLI tool to quickly get the absolute path of files and directories. Or just copy the function and use it in your own POSIX conforming scripts. It turns relative paths into absolute ones and returns absolute ones as is.
Interesting modifications:
If you replace the line result=$(cd "$dir" && pwd)
with result=$(cd "$dir" && pwd -P)
, then all symbolic links in the path to the final file are resolved as well.
If you are not interested into the first modification, you can optimize the absolute case by returning early:
abspath ( ) {
if [ ! -e "$1" ]; then
return 1
fi
case "$1" in
/*)
printf "%s\n" "$1"
return 0
esac
file=""
dir="$1"
if [ ! -d "$dir" ]; then
file=$(basename "$dir")
dir=$(dirname "$dir")
fi
result=$(cd "$dir" && pwd)
if [ -n "$file" ]; then
case "$result" in
*/) ;;
*) result="$result/"
esac
result="$result$file"
fi
printf "%s\n" "$result"
}
And since the question will arise: Why printf
instead of echo
?
echo
is intended primary to print messages for the user to stdout. A lot of echo
behavior that script writers rely on is in fact unspecified. Not even the famous -n
is standardized or the usage of \t
for tab. The POSIX standard says:
A string to be written to standard output. If the first operand is -n, or if any of the operands contain a character, the results are implementation-defined.
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html
Thus whenever you want to write something to stdout and it's not for the purpose of printing a message to the user, the recommendation is to use printf
as the behavior of printf
is exactly defined. My function uses stdout to pass out a result, this is not a message for the user and thus only using printf
guarantees perfect portability.
I use the single line
(cd ${FILENAME%/*}; pwd)
However, this can only be used when $FILENAME
has a leading path of any kind (relative or absolute) that actually exists. If there is no leading path at all, then the answer is simply $PWD
. If the leading path does not exist, then the answer may be indeterminate, otherwise and the answer is simply ${FILENAME%/*}
if the path is absolute.
Putting this all together I would suggest using the following function
function abspath() {
# argument 1: file pathname (relative or absolute)
# returns: file pathname (absolute)
if [ "$1" == "${1##*/}" ]; then # no path at all
echo "$PWD"
elif [ "${1:0:1}" == "/" -a "${1/../}" == "$1" ]; then # strictly absolute path
echo "${1%/*}"
else # relative path (may fail if a needed folder is non-existent)
echo "$(cd ${1%/*}; pwd)"
fi
}
Note also that this only work in bash
and compatible shells. I don't believe the substitutions work in the simple shell sh
.
Hey guys I know it's an old thread but I am just posting this for reference to anybody else who visited this like me. If i understood the question correctly, I think the locate $filename
command. It displays the absolute path of the file supplied, but only if it exists.
locate
is a utility to search for files/paths by name. It may do the job but may also find other matches and may not find an existing file without calling updatedb
as root first.
Success story sharing
realpath
consisting of: github.com/westonruter/misc-cli-tools/blob/master/realpathcoreutils
formula via Homebrew.