I want to transform /foo/bar/..
to /foo
Is there a bash command which does this?
Edit: in my practical case, the directory does exist.
I want to transform /foo/bar/..
to /foo
Is there a bash command which does this?
Edit: in my practical case, the directory does exist.
if you're wanting to chomp part of a filename from the path, "dirname" and "basename" are your friends, and "realpath" is handy too.
dirname /foo/bar/baz
# /foo/bar
basename /foo/bar/baz
# baz
dirname $( dirname /foo/bar/baz )
# /foo
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp
realpath
alternatives
If realpath
is not supported by your shell, you can try
readlink -f /path/here/..
Also
readlink -m /path/there/../../
Works the same as
realpath -s /path/here/../../
in that the path doesn't need to exist to be normalized.
realpath
and readlink
comes from the GNU core utilities, so most probably you get either both or none. If I remember properly, the readlink version on Mac is slightly different than the GNU one :-\ –
Kolk -m
option on realpath
, which normalizes even if the path doesn't exist –
Camphene I don't know if there is a direct bash command to do this, but I usually do
normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"
and it works well.
./
fix it as well? –
Scrag rm -rf $normalDir
) if dirToNormalize does not exist! –
Cressler &&
as per @DavidBlevins's comment. –
Exploiter cd
into the folder (e.g. no execute permissions), then pwd
will return the original folder (which as Frank points out, could be very dangerous) –
Alleyne Try realpath
. Below is the source in its entirety, hereby donated to the public domain.
// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>
static char *s_pMyName;
void usage(void);
int main(int argc, char *argv[])
{
char
sPath[PATH_MAX];
s_pMyName = strdup(basename(argv[0]));
if (argc < 2)
usage();
printf("%s\n", realpath(argv[1], sPath));
return 0;
}
void usage(void)
{
fprintf(stderr, "usage: %s PATH\n", s_pMyName);
exit(1);
}
realpath
. It also happens to be my favourite, but instead of compling your tool from source. It's all to heavyweight, and most of the time you just want readlink -f
... BTW, readlink
is also NOT a bash builtin, but part of coreutils
on Ubuntu. –
Palpitation A portable and reliable solution is to use python, which is preinstalled pretty much everywhere (including Darwin). You have two options:
abspath
returns an absolute path but does not resolve symlinks:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file
realpath
returns an absolute path and in doing so resolves symlinks, generating a canonical path:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
In each case, path/to/file
can be either a relative or absolute path.
readlink
is available on OS X, just not with the -f
option. Portable workarounds discussed here. –
Relational Use the readlink utility from the coreutils package.
MY_PATH=$(readlink -f "$0")
Old question, but there is much simpler way if you are dealing with full path names at the shell level:
abspath="$( cd "$path" && pwd )"
As the cd happens in a subshell it does not impact the main script.
Two variations, supposing your shell built-in commands accept -L and -P, are:
abspath="$( cd -P "$path" && pwd -P )" #physical path with resolved symlinks abspath="$( cd -L "$path" && pwd -L )" #logical path preserving symlinks
Personally, I rarely need this later approach unless I'm fascinated with symbolic links for some reason.
FYI: variation on obtaining the starting directory of a script which works even if the script changes it's current directory later on.
name0="$(basename "$0")"; #base name of script dir0="$( cd "$( dirname "$0" )" && pwd )"; #absolute starting dir
The use of CD assures you always have the absolute directory, even if the script is run by commands such as ./script.sh which, without the cd/pwd, often gives just .. Useless if the script does a cd later on.
readlink
is the bash standard for obtaining the absolute path. It also has the advantage of returning empty strings if paths or a path doesn't exist (given the flags to do so).
To get the absolute path to a directory that may or may not exist, but who's parents do exist, use:
abspath=$(readlink -f $path)
To get the absolute path to a directory that must exist along with all parents:
abspath=$(readlink -e $path)
To canonicalise the given path and follow symlinks if they happen to exist, but otherwise ignore missing directories and just return the path anyway, it's:
abspath=$(readlink -m $path)
The only downside is that readlink will follow links. If you do not want to follow links, you can use this alternative convention:
abspath=$(cd ${path%/*} && echo $PWD/${path##*/})
That will chdir to the directory part of $path and print the current directory along with the file part of $path. If it fails to chdir, you get an empty string and an error on stderr.
readlink
is a good option if it's available. OS X version does not support the -e
or -f
options. In the first three examples, you should have double-quotes around $path
to handle spaces or wildcards in filename. +1 for parameter expansion, but this has a security vulnerability. If path
is empty, this will cd
to your home directory. You need double quotes. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
–
Dougdougal As Adam Liss noted realpath
is not bundled with every distribution. Which is a shame, because it is the best solution. The provided source code is great, and I will probably start using it now. Here is what I have been using until now, which I share here just for completeness:
get_abs_path() {
local PARENT_DIR=$(dirname "$1")
cd "$PARENT_DIR"
local ABS_PATH="$(pwd)"/"$(basename "$1")"
cd - >/dev/null
echo "$ABS_PATH"
}
If you want it to resolve symlinks, just replace pwd
with pwd -P
.
pwd -P
option for this case... Consider what would happen if $(basename "$1")
was a symlink to a file in another directory. The pwd -P
only resolves symlinks in the directory portion of the path, but not the basename portion. –
Dougdougal My recent solution was:
pushd foo/bar/..
dir=`pwd`
popd
Based on the answer of Tim Whitcomb.
pushd $(dirname /usr/bin/java)
a try. –
Irretentive Not exactly an answer but perhaps a follow-up question (original question was not explicit):
readlink
is fine if you actually want to follow symlinks. But there is also a use case for merely normalizing ./
and ../
and //
sequences, which can be done purely syntactically, without canonicalizing symlinks. readlink
is no good for this, and neither is realpath
.
for f in $paths; do (cd $f; pwd); done
works for existing paths, but breaks for others.
A sed
script would seem to be a good bet, except that you cannot iteratively replace sequences (/foo/bar/baz/../..
-> /foo/bar/..
-> /foo
) without using something like Perl, which is not safe to assume on all systems, or using some ugly loop to compare the output of sed
to its input.
FWIW, a one-liner using Java (JDK 6+):
jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
realpath
has a -s
option to not resolve symbolic links and only resolve references to /./
, /../
and remove extra /
characters. When combined with the -m
option, realpath
operates only on the file name, and does not touch any actual file. It sounds like the perfect solution. But alas, realpath
is still missing on many systems. –
Dougdougal ..
components cannot be done syntactically when symlinks are involved. /one/two/../three
is not the same as /one/three
if two
is a symlink to /foo/bar
. –
Balzer I'm late to the party, but this is the solution I've crafted after reading a bunch of threads like this:
resolve_dir() {
(builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}
This will resolve the absolute path of $1, play nice with ~, keep symlinks in the path where they are, and it won't mess with your directory stack. It returns the full path or nothing if it doesn't exist. It expects $1 to be a directory and will probably fail if it's not, but that's an easy check to do yourself.
Talkative, and a bit late answer. I need to write one since I'm stuck on older RHEL4/5. I handles absolute and relative links, and simplifies //, /./ and somedir/../ entries.
test -x /usr/bin/readlink || readlink () {
echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
}
test -x /usr/bin/realpath || realpath () {
local PATH=/bin:/usr/bin
local inputpath=$1
local changemade=1
while [ $changemade -ne 0 ]
do
changemade=0
local realpath=""
local token=
for token in ${inputpath//\// }
do
case $token in
""|".") # noop
;;
"..") # up one directory
changemade=1
realpath=$(dirname $realpath)
;;
*)
if [ -h $realpath/$token ]
then
changemade=1
target=`readlink $realpath/$token`
if [ "${target:0:1}" = '/' ]
then
realpath=$target
else
realpath="$realpath/$target"
fi
else
realpath="$realpath/$token"
fi
;;
esac
done
inputpath=$realpath
done
echo $realpath
}
mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
The problem with realpath
is that it is not available on BSD (or OSX for that matter). Here is a simple recipe extracted from a rather old (2009) article from Linux Journal, that is quite portable:
function normpath() {
# Remove all /./ sequences.
local path=${1//\/.\//\/}
# Remove dir/.. sequences.
while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
path=${path/${BASH_REMATCH[0]}/}
done
echo $path
}
Notice this variant also does not require the path to exist.
I made a builtin-only function to handle this with a focus on highest possible performance (for fun). It does not resolve symlinks, so it is basically the same as realpath -sm
.
## A bash-only mimic of `realpath -sm`.
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () {
${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
local c s p IFS='/'; ## path chunk, absolute path, input path, IFS for splitting paths into chunks
local -i r=0; ## return value
for p in "$@"; do
case "$p" in ## Check for leading backslashes, identify relative/absolute path
'') ((r|=1)); continue;;
//[!/]*) >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
/*) ;;
*) p="$PWD/$p";; ## Prepend the current directory to form an absolute path
esac
s='';
for c in $p; do ## Let IFS split the path at '/'s
case $c in ### NOTE: IFS is '/'; so no quotes needed here
''|.) ;; ## Skip duplicate '/'s and '/./'s
..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
*) s+=/$c;; ### NOTE: No quotes here intentionally. They make no difference, it seems
esac;
done;
echo "${s:-/}"; ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
done
return $r;
}
Note: This function does not handle paths starting with //
, as exactly two double slashes at the start of a path are implementation-defined behavior. However, it handles /
, ///
, and so on just fine.
This function seems to handle all edge cases properly, but there might still be some out there that I haven't dealt with.
Performance Note: when called with thousands of arguments, abspath
runs about 10x slower than realpath -sm
; when called with a single argument, abspath
runs >110x faster than realpath -sm
on my machine, mostly due to not needing to execute a new program every time.
Try our new Bash library product realpath-lib that we have placed on GitHub for free and unencumbered use. It's thoroughly documented and makes a great learning tool.
It resolves local, relative and absolute paths and doesn't have any dependencies except Bash 4+; so it should work just about anywhere. It's free, clean, simple and instructive.
You can do:
get_realpath <absolute|relative|symlink|local file path>
This function is the core of the library:
function get_realpath() {
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
It also contains functions to get_dirname, get_filename, get_ stemname and validate_path. Try it across platforms, and help to improve it.
Based on @Andre's answer, I might have a slightly better version, in case someone is after a loop-free, completely string-manipulation based solution. It is also useful for those who don't want to dereference any symlinks, which is the downside of using realpath
or readlink -f
.
It works on bash versions 3.2.25 and higher.
shopt -s extglob
normalise_path() {
local path="$1"
# get rid of /../ example: /one/../two to /two
path="${path//\/*([!\/])\/\.\./}"
# get rid of /./ and //* example: /one/.///two to /one/two
path="${path//@(\/\.\/|\/+(\/))//}"
# remove the last '/.'
echo "${path%%/.}"
}
$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
hello/../world
(2) Dots in filename /hello/..world
(3) Dots after double-slash /hello//../world
(4) Dot before or after double-slash /hello//./world
or /hello/.//world
(5) Parent after current: /hello/./../world/
(6) Parent after Parent: /hello/../../world
, etc. -- Some of these can be fixed by using a loop to make corrections until the path stops changing. (Also remove dir/../
, not /dir/..
but remove dir/..
from the end.) –
Mimimimic If you just want to normalize a path, existed or not existed, without touching the file system, without resolving any links, and without external utils, here is a pure Bash function translated from Python's posixpath.normpath
.
#!/usr/bin/env bash
# Normalize path, eliminating double slashes, etc.
# Usage: new_path="$(normpath "${old_path}")"
# Translated from Python's posixpath.normpath:
# https://github.com/python/cpython/blob/master/Lib/posixpath.py#L337
normpath() {
local IFS=/ initial_slashes='' comp comps=()
if [[ $1 == /* ]]; then
initial_slashes='/'
[[ $1 == //* && $1 != ///* ]] && initial_slashes='//'
fi
for comp in $1; do
[[ -z ${comp} || ${comp} == '.' ]] && continue
if [[ ${comp} != '..' || (-z ${initial_slashes} && ${#comps[@]} -eq 0) || (\
${#comps[@]} -gt 0 && ${comps[-1]} == '..') ]]; then
comps+=("${comp}")
elif ((${#comps[@]})); then
unset 'comps[-1]'
fi
done
comp="${initial_slashes}${comps[*]}"
printf '%s\n' "${comp:-.}"
}
Examples:
new_path="$(normpath '/foo/bar/..')"
echo "${new_path}"
# /foo
normpath "relative/path/with trailing slashs////"
# relative/path/with trailing slashs
normpath "////a/../lot/././/mess////./here/./../"
# /lot/mess
normpath ""
# .
# (empty path resolved to dot)
Personally, I cannot understand why Shell, a language often used for manipulating files, doesn't offer basic functions to deal with paths. In python, we have nice libraries like os.path or pathlib, which offers a whole bunch of tools to extract filename, extension, basename, path segments, split or join paths, to get absolute or normalized paths, to determine relations between paths, to do everything without much brain. And they take care of edge cases, and they're reliable. In Shell, to do any of these, either we call external executables, or we have to reinvent wheels with these extremely rudimentary and arcane syntaxes...
I needed a solution that would do all three:
realpath
and readlink -f
are addonsNone of the answers had both #1 and #2. I added #3 to save others any further yak-shaving.
#!/bin/bash
P="${1?Specify a file path}"
[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }
while [ -h "$P" ] ; do
ls="$(ls -ld "$P")"
link="$(expr "$ls" : '.*-> \(.*\)$')"
expr "$link" : '/.*' > /dev/null &&
P="$link" ||
P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"
Here is a short test case with some twisted spaces in the paths to fully exercise the quoting
mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "
cd "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
Based on loveborg's excellent python snippet, I wrote this:
#!/bin/sh
# Version of readlink that follows links to the end; good for Mac OS X
for file in "$@"; do
while [ -h "$file" ]; do
l=`readlink $file`
case "$l" in
/*) file="$l";;
*) file=`dirname "$file"`/"$l"
esac
done
#echo $file
python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)
This works even if the file doesn't exist. It does require the directory containing the file to exist.
realpath -e
–
Creaky I know this is an ancient question. I'm still offering an alternative. Recently I met the same issue and found no existing and portable command to do that. So I wrote the following shell script which includes a function that can do the trick.
#! /bin/sh
function normalize {
local rc=0
local ret
if [ $# -gt 0 ] ; then
# invalid
if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
echo $1
return -1
fi
# convert to absolute path
if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
normalize "`pwd`/$1"
return $?
fi
ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
else
read line
normalize "$line"
return $?
fi
if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
ret=`normalize "$ret"`
rc=$?
fi
echo "$ret"
return $rc
}
https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c
Since none of the presented solutions worked for me, in the case where a file does not exist, I implemented my idea. The solution of André Anjos had the problem that paths beginning with ../../ were resolved wrongly. For example ../../a/b/ became a/b/.
function normalize_rel_path(){
local path=$1
result=""
IFS='/' read -r -a array <<< "$path"
i=0
for (( idx=${#array[@]}-1 ; idx>=0 ; idx-- )) ; do
c="${array[idx]}"
if [ -z "$c" ] || [[ "$c" == "." ]];
then
continue
fi
if [[ "$c" == ".." ]]
then
i=$((i+1))
elif [ "$i" -gt "0" ];
then
i=$((i-1))
else
if [ -z "$result" ];
then
result=$c
else
result=$c/$result
fi
fi
done
while [ "$i" -gt "0" ]; do
i=$((i-1))
result="../"$result
done
unset IFS
echo $result
}
I discovered today that you can use the stat
command to resolve paths.
So for a directory like "~/Documents":
You can run this:
stat -f %N ~/Documents
To get the full path:
/Users/me/Documents
For symlinks, you can use the %Y format option:
stat -f %Y example_symlink
Which might return a result like:
/usr/local/sbin/example_symlink
The formatting options might be different on other versions of *NIX but these worked for me on OSX.
stat -f %N ~/Documents
line is a red herring... your shell is replacing ~/Documents
with /Users/me/Documents
, and stat
is just printing its argument verbatim. –
Kept A simple solution using node.js
:
#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
© 2022 - 2024 — McMap. All rights reserved.
/foo/bar
or even/foo
actually exist, or are you only interested in the string manipulation aspect according to path name rules? – Scilla/foo/bar/..
to/foo
, and using abash
command. If there are other requirements that are not stated, then perhaps they should be... – Tiffaneytiffani