Related
I am trying to see if the /dev/rfcomm0 file exists but in the way as described below by using the normal [ -f $file ] way I constantly get the message that the file does not exist. But I am able to read the file manually with cat.
looking for /etc/hosts is just for controling that I'm not getting insane.
file="/dev/rfcomm0"
file2="/etc/hosts"
while :
do
[ -f $file2 ] && echo "Found host" || echo "Not found host"
[ -f $file ] && echo "Found rfcomm" || echo "not found rfcomm"
the manual view of /dev/rfcomm
root#raspberrypi:~# cat /dev/rfcomm0
[null,"0,0,0","255,255,255",5,50,"Nikske"]
Probably I'm missing something very important but I've been looking for a long time after a solution.
The -f flag specifically states in the bash documentation (my emphasis):
True if file exists and is a regular file
So I'd warrant that the file you're looking at is not a regular file, especially since it's in the /dev directory where all sorts of non-regular files tend to exist.
Case in point (on my system with a character-special file):
pax$ ls -al /dev/rfkill
crw-rw-r--+ 1 root netdev 10, 62 Feb 13 17:49 /dev/rfkill
pax$ [[ -f /dev/rfkill ]] && echo regular || echo not regular
not regular
pax$ [[ -e /dev/rfkill ]] && echo exists || echo does not exist
exists
pax$ [[ -c /dev/rfkill ]] && echo char special || echo not char special
char special
I'd suggest checking what sort of file it is with ls -al and using the appropriate [[ flag for that (see, for example, CONDITIONAL EXPRESSIONS in the output of man bash).
This question already has answers here:
How can I check if a program exists from a Bash script?
(39 answers)
Closed 4 years ago.
In a bash script, I need to determine whether an executable named foo is on the PATH.
You could also use the Bash builtin type -P:
help type
cmd=ls
[[ $(type -P "$cmd") ]] && echo "$cmd is in PATH" ||
{ echo "$cmd is NOT in PATH" 1>&2; exit 1; }
You can use which:
path_to_executable=$(which name_of_executable)
if [ -x "$path_to_executable" ] ; then
echo "It's here: $path_to_executable"
fi
TL;DR:
In bash:
function is_bin_in_path {
builtin type -P "$1" &> /dev/null
}
Example usage of is_bin_in_path:
% is_bin_in_path ls && echo "found in path" || echo "not in path"
found in path
In zsh:
Use whence -p instead.
For a version that works in both {ba,z}sh:
# True if $1 is an executable in $PATH
# Works in both {ba,z}sh
function is_bin_in_path {
if [[ -n $ZSH_VERSION ]]; then
builtin whence -p "$1" &> /dev/null
else # bash:
builtin type -P "$1" &> /dev/null
fi
}
To test that ALL given commands are executables in $PATH:
# True iff all arguments are executable in $PATH
function is_bin_in_path {
if [[ -n $ZSH_VERSION ]]; then
builtin whence -p "$1" &> /dev/null
else # bash:
builtin type -P "$1" &> /dev/null
fi
[[ $? -ne 0 ]] && return 1
if [[ $# -gt 1 ]]; then
shift # We've just checked the first one
is_bin_in_path "$#"
fi
}
Example usage:
is_bin_in_path ssh-agent ssh-add && setup_ssh_agent
Non-solutions to avoid
This is not a short answer because the solution must correctly handle:
Functions
Aliases
Builtin commands
Reserved words
Examples which fail with plain type (note the token after type changes):
$ alias foo=ls
$ type foo && echo "in path" || echo "not in path"
foo is aliased to `ls'
in path
$ type type && echo "in path" || echo "not in path"
type is a shell builtin
in path
$ type if && echo "in path" || echo "not in path"
if is a shell keyword
in path
Note that in bash, which is not a shell builtin (it is in zsh):
$ PATH=/bin
$ builtin type which
which is /bin/which
This answer says why to avoid using which:
Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
Why care?
Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.
In this case, also avoid command -v
The answer I just quoted from suggests using command -v, however this doesn't apply to the current "is the executable in $PATH?" scenario: it will fail in exactly the ways I've illustrated with plain type above.
Correct solutions
In bash we need to use type -P:
-P force a PATH search for each NAME, even if it is an alias,
builtin, or function, and returns the name of the disk file
that would be executed
In zsh we need to use whence -p:
-p Do a path search for name even if it is an alias,
reserved word, shell function or builtin.
You can use the command builtin, which is POSIX compatible:
if [ -x "$(command -v "$cmd")" ]; then
echo "$cmd is in \$PATH"
fi
The executable check is needed because command -v detects functions and aliases as well as executables.
In Bash, you can also use type with the -P option, which forces a PATH search:
if type -P "$cmd" &>/dev/null; then
echo "$cmd is in \$PATH"
fi
As already mentioned in the comments, avoid which as it requires launching an external process and might give you incorrect output in some cases.
if command -v foo ; then foo ; else echo "foo unavailable" ; fi
Use which
$ which myprogram
We can define a function for checking whether as executable exists by using which:
function is_executable() {
which "$#" &> /dev/null
}
The function is called just like you would call an executable. "$#" ensures that which gets exactly the same arguments as are given to the function.
&> /dev/null ensures that whatever is written to stdout or stderr by which is redirected to the null device (which is a special device which discards the information written to it) and not written to stdout or stderr by the function.
Since the function doesn't explicitly return with an return code, when it does return, the exit code of the latest executed executable—which in this case is which—will be the return code of the function. which will exit with a code that indicates success if it is able to find the executable specified by the argument to the function, otherwise with an exit code that indicates failure. This behavior will automatically be replicated by is_executable.
We can then use that function to conditionally do something:
if is_executable name_of_executable; then
echo "name_of_executable was found"
else
echo "name_of_executable was NOT found"
fi
Here, if executes the command(s) written between it and then—which in our case is is_executable name_of_executable—and chooses the branch to execute based on the return code of the command(s).
Alternatively, we can skip defining the function and use which directly in the if-statement:
if which name_of_executable &> /dev/null; then
echo "name_of_executable was found"
else
echo "name_of_executable was NOT found"
fi
However, I think this makes the code slightly less readable.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about a specific programming problem, a software algorithm, or software tools primarily used by programmers. If you believe the question would be on-topic on another Stack Exchange site, you can leave a comment to explain where the question may be able to be answered.
Closed last year.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
Improve this question
On Linux, the readlink utility accepts an option -f that follows additional links. This doesn't seem to work on Mac and possibly BSD based systems. What would the equivalent be?
Here's some debug information:
$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]
MacPorts and Homebrew provide a coreutils package containing greadlink (GNU readlink). Credit to Michael Kallweitt post in mackb.com.
brew install coreutils
greadlink -f file.txt
readlink -f does two things:
It iterates along a sequence of symlinks until it finds an actual file.
It returns that file's canonicalized name—i.e., its absolute pathname.
If you want to, you can just build a shell script that uses vanilla readlink behavior to achieve the same thing. Here's an example. Obviously you could insert this in your own script where you'd like to call readlink -f
#!/bin/sh
TARGET_FILE=$1
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
TARGET_FILE=`readlink $TARGET_FILE`
cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`
done
# Compute the canonicalized name by finding the physical path
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT
Note that this doesn't include any error handling. Of particular importance, it doesn't detect symlink cycles. A simple way to do this would be to count the number of times you go around the loop and fail if you hit an improbably large number, such as 1,000.
EDITED to use pwd -P instead of $PWD.
Note that this script expects to be called like ./script_name filename, no -f, change $1 to $2 if you want to be able to use with -f filename like GNU readlink.
You may be interested in realpath(3), or Python's os.path.realpath. The two aren't exactly the same; the C library call requires that intermediary path components exist, while the Python version does not.
$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r-- 1 miles wheel 0 Jul 11 21:08 a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 b -> a
lrwxr-xr-x 1 miles wheel 1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a
I know you said you'd prefer something more lightweight than another scripting language, but just in case compiling a binary is insufferable, you can use Python and ctypes (available on Mac OS X 10.5) to wrap the library call:
#!/usr/bin/python
import ctypes, sys
libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p
def realpath(path):
buffer = ctypes.create_string_buffer(1024) # PATH_MAX
if libc.realpath(path, buffer):
return buffer.value
else:
errno = libc.__error().contents.value
raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))
if __name__ == '__main__':
print realpath(sys.argv[1])
Ironically, the C version of this script ought to be shorter. :)
A simple one-liner in perl that's sure to work almost everywhere without any external dependencies:
perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file
Will dereference symlinks.
Usage in a script could be like this:
readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"
You might need both a portable, pure shell implementation, and unit-test coverage, as the number of edge-cases for something like this is non-trivial.
See my project on Github for tests and full code. What follows is a synopsis of the implementation:
As Keith Smith astutely points out, readlink -f does two things: 1) resolves symlinks recursively, and 2) canonicalizes the result, hence:
realpath() {
canonicalize_path "$(resolve_symlinks "$1")"
}
First, the symlink resolver implementation:
resolve_symlinks() {
local dir_context path
path=$(readlink -- "$1")
if [ $? -eq 0 ]; then
dir_context=$(dirname -- "$1")
resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
else
printf '%s\n' "$1"
fi
}
_prepend_path_if_relative() {
case "$2" in
/* ) printf '%s\n' "$2" ;;
* ) printf '%s\n' "$1/$2" ;;
esac
}
Note that this is a slightly simplified version of the full implementation. The full implementation adds a small check for symlink cycles, as well as massages the output a bit.
Finally, the function for canonicalizing a path:
canonicalize_path() {
if [ -d "$1" ]; then
_canonicalize_dir_path "$1"
else
_canonicalize_file_path "$1"
fi
}
_canonicalize_dir_path() {
(cd "$1" 2>/dev/null && pwd -P)
}
_canonicalize_file_path() {
local dir file
dir=$(dirname -- "$1")
file=$(basename -- "$1")
(cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}
That's it, more or less. Simple enough to paste into your script, but tricky enough that you'd be crazy to rely on any code that doesn't have unit tests for your use cases.
Install homebrew
Run "brew install coreutils"
Run "greadlink -f path"
greadlink is the gnu readlink that implements -f. You can use macports or others as well, I prefer homebrew.
I made a script called realpath personally which looks a little something like:
#!/usr/bin/env python
import os, sys
print(os.path.realpath(sys.argv[1]))
What about this?
function readlink() {
DIR="${1%/*}"
(cd "$DIR" && echo "$(pwd -P)")
}
A lazy way that works for me,
$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink
Implementation
Install brew
Follow the instructions at https://brew.sh/
Install the coreutils package
brew install coreutils
Create an Alias or Symlink
3a. Create an an alias (per user)
You can place your alias in ~/.bashrc, ~/.bash_profile, or wherever you are used to keeping your bash aliases. I personally keep mine in ~/.bashrc
alias readlink=greadlink
3b. Create a symbolic link (system wide)
ln -s /usr/local/bin/greadlink /usr/local/bin/readlink (credit: Izana)
This will create a symbolic link in /usr/local/bin while keeping the original readlink binary in tact. It works because the search for readlink will return 2 results. But the second in /usr/local/bin will take precedence.
e.g. which readlink
To undo this change simply unlink /usr/local/bin/readlink
Additional Tools
You can create similar aliases or symlinks for other coreutils such as gmv, gdu, gdf, and so on. But beware that the GNU behavior on a mac machine may be confusing to others used to working with native coreutils, or may behave in unexpected ways on your mac system.
Explanation
coreutils is a brew package that installs GNU/Linux core utilities which correspond to the Mac OSX implementation of them so that you can use those
You may find programs or utilties on your mac osx system which seem similar to Linux coreutils ("Core Utilities") yet they differ in some ways (such as having different flags).
This is because the Mac OSX implementation of these tools are different. To get the original GNU/Linux-like behavior you can install the coreutils package via the brew package management system.
This will install corresponding core utilities, prefixed by g. E.g. for readlink, you will find a corresponding greadlink program.
In order to make readlink perform like the GNU readlink (greadlink) implementation, you can create a simple alias or symbolic link after you install coreutils.
FreeBSD and OSX have a version of statderived from NetBSD.
You can adjust the output with format switches (see the manual pages at the links above).
% cd /service
% ls -tal
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------ 3 root wheel 8 Jun 30 13:59 .s6-svscan
drwxr-xr-x 3 root wheel 5 Jun 30 13:34 .
lrwxr-xr-x 1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x 1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y clockspeed-adjust
/var/service/clockspeed-adjust
Some OS X versions of stat may lack the -f%R option for formats. In this case -stat -f%Y may suffice. The -f%Y option will show the target of a symlink, whereas -f%R shows the absolute pathname corresponding to the file.
EDIT:
If you're able to use Perl (Darwin/OS X comes installed with recent verions of perl) then:
perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt
will work.
The easiest way to solve this problem and enable the functionality of readlink on Mac w/ Homebrew installed or FreeBSD is to install 'coreutils' package. May also be necessary on certain Linux distributions and other POSIX OS.
For example, in FreeBSD 11, I installed by invoking:
# pkg install coreutils
On MacOS with Homebrew, the command would be:
$ brew install coreutils
Not really sure why the other answers are so complicated, that's all there is to it. The files aren't in a different place, they're just not installed yet.
Here is a portable shell function that should work in ANY Bourne comparable shell.
It will resolve the relative path punctuation ".. or ." and dereference symbolic links.
If for some reason you do not have a realpath(1) command, or readlink(1) this can be aliased.
which realpath || alias realpath='real_path'
Enjoy:
real_path () {
OIFS=$IFS
IFS='/'
for I in $1
do
# Resolve relative path punctuation.
if [ "$I" = "." ] || [ -z "$I" ]
then continue
elif [ "$I" = ".." ]
then FOO="${FOO%%/${FOO##*/}}"
continue
else FOO="${FOO}/${I}"
fi
## Resolve symbolic links
if [ -h "$FOO" ]
then
IFS=$OIFS
set `ls -l "$FOO"`
while shift ;
do
if [ "$1" = "->" ]
then FOO=$2
shift $#
break
fi
done
IFS='/'
fi
done
IFS=$OIFS
echo "$FOO"
}
also, just in case anybody is interested here is how to implement basename and dirname in 100% pure shell code:
## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
echo "${1%/*}"
}
## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
echo "${1##*/}"
}
You can find updated version of this shell code at my google site: http://sites.google.com/site/jdisnard/realpath
EDIT:
This code is licensed under the terms of the 2-clause (freeBSD style) license.
A copy of the license may be found by following the above hyperlink to my site.
Begin Update
This is such a frequent problem that we have put together a Bash 4 library for free use (MIT License) called realpath-lib. This is designed to emulate readlink -f by default and includes two test suites to verify (1) that it works for a given unix system and (2) against readlink -f if installed (but this is not required). Additionally, it can be used to investigate, identify and unwind deep, broken symlinks and circular references, so it can be a useful tool for diagnosing deeply-nested physical or symbolic directory and file problems. It can be found at github.com or bitbucket.org.
End Update
Another very compact and efficient solution that does not rely on anything but Bash is:
function get_realpath() {
[[ ! -f "$1" ]] && return 1 # failure : file does not exist.
[[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
return 0 # success
}
This also includes an environment setting no_symlinks that provides the ability to resolve symlinks to the physical system. As long as no_symlinks is set to something, ie no_symlinks='on' then symlinks will be resolved to the physical system. Otherwise they will be applied (the default setting).
This should work on any system that provides Bash, and will return a Bash compatible exit code for testing purposes.
There are already a lot of answers, but none worked for me... So this is what I'm using now.
readlink_f() {
local target="$1"
[ -f "$target" ] || return 1 #no nofile
while [ -L "$target" ]; do
target="$(readlink "$target")"
done
echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}
Since my work is used by people with non-BSD Linux as well as macOS, I've opted for using these aliases in our build scripts (sed included since it has similar issues):
##
# If you're running macOS, use homebrew to install greadlink/gsed first:
# brew install coreutils
#
# Example use:
# # Gets the directory of the currently running script
# dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
# alias al='pico ${dotfilesDir}/aliases.local'
##
function globalReadlink () {
# Use greadlink if on macOS; otherwise use normal readlink
if [[ $OSTYPE == darwin* ]]; then
greadlink "$#"
else
readlink "$#"
fi
}
function globalSed () {
# Use gsed if on macOS; otherwise use normal sed
if [[ $OSTYPE == darwin* ]]; then
gsed "$#"
else
sed "$#"
fi
}
Optional check you could add to automatically install homebrew + coreutils dependencies:
if [[ "$OSTYPE" == "darwin"* ]]; then
# Install brew if needed
if [ -z "$(which brew)" ]; then
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)";
fi
# Check for coreutils
if [ -z "$(brew ls coreutils)" ]; then
brew install coreutils
fi
fi
I suppose to be truly "global" it needs to check others...but that probably comes close to the 80/20 mark.
POSIX compliant readlink -f implementation for POSIX shell scripts
https://github.com/ko1nksm/readlinkf
This is POSIX compliant (no bashism). It uses neither readlink nor realpath. I have verified that it is exactly the same by comparing with GNU readlink -f (see test results). It has error handling and good performance. You can safely replace from readlink -f. The license is CC0, so you can use it for any project.
This code is adopted in the bats-core project.
# POSIX compliant version
readlinkf_posix() {
[ "${1:-}" ] || return 1
max_symlinks=40
CDPATH='' # to avoid changing to an unexpected directory
target=$1
[ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
[ -d "${target:-/}" ] && target="$target/"
cd -P . 2>/dev/null || return 1
while [ "$max_symlinks" -ge 0 ] && max_symlinks=$((max_symlinks - 1)); do
if [ ! "$target" = "${target%/*}" ]; then
case $target in
/*) cd -P "${target%/*}/" 2>/dev/null || break ;;
*) cd -P "./${target%/*}" 2>/dev/null || break ;;
esac
target=${target##*/}
fi
if [ ! -L "$target" ]; then
target="${PWD%/}${target:+/}${target}"
printf '%s\n' "${target:-/}"
return 0
fi
# `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
# <file mode>, <number of links>, <owner name>, <group name>,
# <size>, <date and time>, <pathname of link>, <contents of link>
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
link=$(ls -dl -- "$target" 2>/dev/null) || break
target=${link#*" $target -> "}
done
return 1
}
Please refer to the latest code. It may some fixed.
Better late than never, I suppose. I was motivated to develop this specifically because my Fedora scripts weren't working on the Mac. The problem is dependencies and Bash. Macs don't have them, or if they do, they are often somewhere else (another path). Dependency path manipulation in a cross-platform Bash script is a headache at best and a security risk at worst - so it's best to avoid their use, if possible.
The function get_realpath() below is simple, Bash-centric, and no dependencies are required. I uses only the Bash builtins echo and cd. It is also fairly secure, as everything gets tested at each stage of the way and it returns error conditions.
If you don't want to follow symlinks, then put set -P at the front of the script, but otherwise cd should resolve the symlinks by default. It's been tested with file arguments that are {absolute | relative | symlink | local} and it returns the absolute path to the file. So far we've not had any problems with it.
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
}
You can combine this with other functions get_dirname, get_filename, get_stemname and validate_path. These can be found at our GitHub repository as realpath-lib (full disclosure - this is our product but we offer it free to the community without any restrictions). It also could serve as a instructional tool - it's well documented.
We've tried our best to apply so-called 'modern Bash' practices, but Bash is a big subject and I'm certain there will always be room for improvement. It requires Bash 4+ but could be made to work with older versions if they are still around.
echo $(cd $(dirname file1) ; pwd -P)
I wrote a realpath utility for OS X which can provide the same results as readlink -f.
Here is an example:
(jalcazar#mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11 8月 25 19:29 a -> /etc/passwd
(jalcazar#mac tmp)$ realpath a
/etc/passwd
If you are using MacPorts, you can install it with the following command: sudo port selfupdate && sudo port install realpath.
Truely platform-indpendent would be also this R-onliner
readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}
To actually mimic readlink -f <path>, $2 instead of $1 would need to be used.
I have simply pasted the following to the top of my bash scripts:
#!/usr/bin/env bash -e
declare script=$(basename "$0")
declare dirname=$(dirname "$0")
declare scriptDir
if [[ $(uname) == 'Linux' ]];then
# use readlink -f
scriptDir=$(readlink -f "$dirname")
else
# can't use readlink -f, do a pwd -P in the script directory and then switch back
if [[ "$dirname" = '.' ]];then
# don't change directory, we are already inside
scriptDir=$(pwd -P)
else
# switch to the directory and then switch back
pwd=$(pwd)
cd "$dirname"
scriptDir=$(pwd -P)
cd "$pwd"
fi
fi
And removed all instances of readlink -f. $scriptDir and $script then will be available for the rest of the script.
While this does not follow all symlinks, it works on all systems and appears to be good enough for most use cases, it switches the directory into the containing folder, and then it does a pwd -P to get the real path of that directory, and then finally switch back to the original.
Perl has a readlink function (e.g. How do I copy symbolic links in Perl?). This works across most platforms, including OS X:
perl -e "print readlink '/path/to/link'"
For example:
$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c
The answer from #Keith Smith gives an infinite loop.
Here is my answer, which i use only on SunOS (SunOS miss so much POSIX and GNU commands).
It's a script file you have to put in one of your $PATH directories:
#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99
link="$1"
while [ -L "$link" ]; do
lastLink="$link"
link=$(/bin/ls -ldq "$link")
link="${link##* -> }"
link=$(realpath "$link")
[ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done
echo "$link"
This is what I use:
stat -f %N $your_path
The paths to readlink are different between my system and yours. Please try specifying the full path:
/sw/sbin/readlink -f
How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?
It seems like it should be easy, but it's been stumping me.
Answer
POSIX compatible:
command -v <the_command>
Example use:
if ! command -v <the_command> &> /dev/null
then
echo "<the_command> could not be found"
exit
fi
For Bash specific environments:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Explanation
Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
Why care?
Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.
So, don't use which. Instead use one of these:
command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
(Minor side-note: some will suggest 2>&- is the same 2>/dev/null but shorter – this is untrue. 2>&- closes FD 2 which causes an error in the program when it tries to write to stderr, which is very different from successfully writing to it and discarding the output (and dangerous!))
If your hash bang is /bin/sh then you should care about what POSIX says. type and hash's exit codes aren't terribly well defined by POSIX, and hash is seen to exit successfully when the command doesn't exist (haven't seen this with type yet). command's exit status is well defined by POSIX, so that one is probably the safest to use.
If your script uses bash though, POSIX rules don't really matter anymore and both type and hash become perfectly safe to use. type now has a -P to search just the PATH and hash has the side-effect that the command's location will be hashed (for faster lookup next time you use it), which is usually a good thing since you probably check for its existence in order to actually use it.
As a simple example, here's a function that runs gdate if it exists, otherwise date:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$#"
else
date "$#"
fi
}
Alternative with a complete feature set
You can use scripts-common to reach your need.
To check if something is installed, you can do:
checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."
The following is a portable way to check whether a command exists in $PATH and is executable:
[ -x "$(command -v foo)" ]
Example:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
The executable check is needed because bash returns a non-executable file if no executable file with that name is found in $PATH.
Also note that if a non-executable file with the same name as the executable exists earlier in $PATH, dash returns the former, even though the latter would be executed. This is a bug and is in violation of the POSIX standard. [Bug report] [Standard]
Edit: This seems to be fixed as of dash 0.5.11 (Debian 11).
In addition, this will fail if the command you are looking for has been defined as an alias.
I agree with lhunath to discourage use of which, and his solution is perfectly valid for Bash users. However, to be more portable, command -v shall be used instead:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; }
Command command is POSIX compliant. See here for its specification: command - execute a simple command
Note: type is POSIX compliant, but type -P is not.
It depends on whether you want to know whether it exists in one of the directories in the $PATH variable or whether you know the absolute location of it. If you want to know if it is in the $PATH variable, use
if which programname >/dev/null; then
echo exists
else
echo does not exist
fi
otherwise use
if [ -x /path/to/programname ]; then
echo exists
else
echo does not exist
fi
The redirection to /dev/null/ in the first example suppresses the output of the which program.
I have a function defined in my .bashrc that makes this easier.
command_exists () {
type "$1" &> /dev/null ;
}
Here's an example of how it's used (from my .bash_profile.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
Expanding on #lhunath's and #GregV's answers, here's the code for the people who want to easily put that check inside an if statement:
exists()
{
command -v "$1" >/dev/null 2>&1
}
Here's how to use it:
if exists bash; then
echo 'Bash exists!'
else
echo 'Your system does not have Bash'
fi
Try using:
test -x filename
or
[ -x filename ]
From the Bash manpage under Conditional Expressions:
-x file
True if file exists and is executable.
To use hash, as #lhunath suggests, in a Bash script:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
This script runs hash and then checks if the exit code of the most recent command, the value stored in $?, is equal to 1. If hash doesn't find foo, the exit code will be 1. If foo is present, the exit code will be 0.
&> /dev/null redirects standard error and standard output from hash so that it doesn't appear onscreen and echo >&2 writes the message to standard error.
Command -v works fine if the POSIX_BUILTINS option is set for the <command> to test for, but it can fail if not. (It has worked for me for years, but I recently ran into one where it didn't work.)
I find the following to be more failproof:
test -x "$(which <command>)"
Since it tests for three things: path, existence and execution permission.
There are a ton of options here, but I was surprised no quick one-liners. This is what I used at the beginning of my scripts:
[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }
This is based on the selected answer here and another source.
If you check for program existence, you are probably going to run it later anyway. Why not try to run it in the first place?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
It's a more trustworthy check that the program runs than merely looking at PATH directories and file permissions.
Plus you can get some useful result from your program, such as its version.
Of course the drawbacks are that some programs can be heavy to start and some don't have a --version option to immediately (and successfully) exit.
Check for multiple dependencies and inform status to end users
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
Sample output:
latex OK
pandoc missing
Adjust the 10 to the maximum command length. It is not automatic, because I don't see a non-verbose POSIX way to do it:
How can I align the columns of a space separated table in Bash?
Check if some apt packages are installed with dpkg -s and install them otherwise.
See: Check if an apt-get package is installed and then install it if it's not on Linux
It was previously mentioned at: How can I check if a program exists from a Bash script?
I never did get the previous answers to work on the box I have access to. For one, type has been installed (doing what more does). So the builtin directive is needed. This command works for me:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
I wanted the same question answered but to run within a Makefile.
install:
#if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
It could be simpler, just:
#!/usr/bin/env bash
set -x
# if local program 'foo' returns 1 (doesn't exist) then...
if ! type -P foo; then
echo 'crap, no foo'
else
echo 'sweet, we have foo!'
fi
Change foo to vi to get the other condition to fire.
hash foo 2>/dev/null: works with Z shell (Zsh), Bash, Dash and ash.
type -p foo: it appears to work with Z shell, Bash and ash (BusyBox), but not Dash (it interprets -p as an argument).
command -v foo: works with Z shell, Bash, Dash, but not ash (BusyBox) (-ash: command: not found).
Also note that builtin is not available with ash and Dash.
zsh only, but very useful for zsh scripting (e.g. when writing completion scripts):
The zsh/parameter module gives access to, among other things, the internal commands hash table. From man zshmodules:
THE ZSH/PARAMETER MODULE
The zsh/parameter module gives access to some of the internal hash ta‐
bles used by the shell by defining some special parameters.
[...]
commands
This array gives access to the command hash table. The keys are
the names of external commands, the values are the pathnames of
the files that would be executed when the command would be in‐
voked. Setting a key in this array defines a new entry in this
table in the same way as with the hash builtin. Unsetting a key
as in `unset "commands[foo]"' removes the entry for the given
key from the command hash table.
Although it is a loadable module, it seems to be loaded by default, as long as zsh is not used with --emulate.
example:
martin#martin ~ % echo $commands[zsh]
/usr/bin/zsh
To quickly check whether a certain command is available, just check if the key exists in the hash:
if (( ${+commands[zsh]} ))
then
echo "zsh is available"
fi
Note though that the hash will contain any files in $PATH folders, regardless of whether they are executable or not. To be absolutely sure, you have to spend a stat call on that:
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
echo "zsh is available"
fi
The which command might be useful. man which
It returns 0 if the executable is found and returns 1 if it's not found or not executable:
NAME
which - locate a command
SYNOPSIS
which [-a] filename ...
DESCRIPTION
which returns the pathnames of the files which would
be executed in the current environment, had its
arguments been given as commands in a strictly
POSIX-conformant shell. It does this by searching
the PATH for executable files matching the names
of the arguments.
OPTIONS
-a print all matching pathnames of each argument
EXIT STATUS
0 if all specified commands are
found and executable
1 if one or more specified commands is nonexistent
or not executable
2 if an invalid option is specified
The nice thing about which is that it figures out if the executable is available in the environment that which is run in - it saves a few problems...
Use Bash builtins if you can:
which programname
...
type -P programname
For those interested, none of the methodologies in previous answers work if you wish to detect an installed library. I imagine you are left either with physically checking the path (potentially for header files and such), or something like this (if you are on a Debian-based distribution):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
As you can see from the above, a "0" answer from the query means the package is not installed. This is a function of "grep" - a "0" means a match was found, a "1" means no match was found.
This will tell according to the location if the program exist or not:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
I'd say there isn't any portable and 100% reliable way due to dangling aliases. For example:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Of course, only the last one is problematic (no offence to Ringo!). But all of them are valid aliases from the point of view of command -v.
In order to reject dangling ones like ringo, we have to parse the output of the shell built-in alias command and recurse into them (command -v isn't a superior to alias here.) There isn't any portable solution for it, and even a Bash-specific solution is rather tedious.
Note that a solution like this will unconditionally reject alias ls='ls -F':
test() { command -v $1 | grep -qv alias }
If you guys/gals can't get the things in answers here to work and are pulling hair out of your back, try to run the same command using bash -c. Just look at this somnambular delirium. This is what really happening when you run $(sub-command):
First. It can give you completely different output.
$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls
Second. It can give you no output at all.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
#!/bin/bash
a=${apt-cache show program}
if [[ $a == 0 ]]
then
echo "the program doesn't exist"
else
echo "the program exists"
fi
#program is not literal, you can change it to the program's name you want to check
The hash-variant has one pitfall: On the command line you can for example type in
one_folder/process
to have process executed. For this the parent folder of one_folder must be in $PATH. But when you try to hash this command, it will always succeed:
hash one_folder/process; echo $? # will always output '0'
I second the use of "command -v". E.g. like this:
md=$(command -v mkdirhier) ; alias md=${md:=mkdir} # bash
emacs="$(command -v emacs) -nw" || emacs=nano
alias e=$emacs
[[ -z $(command -v jed) ]] && alias jed=$emacs
I had to check if Git was installed as part of deploying our CI server. My final Bash script was as follows (Ubuntu server):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
To mimic Bash's type -P cmd, we can use the POSIX compliant env -i type cmd 1>/dev/null 2>&1.
man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.
ls() { echo 'Hello, world!'; }
ls
type ls
env -i type ls
cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
If there isn't any external type command available (as taken for granted here), we can use POSIX compliant env -i sh -c 'type cmd 1>/dev/null 2>&1':
# Portable version of Bash's type -P cmd (without output on stdout)
typep() {
command -p env -i PATH="$PATH" sh -c '
export LC_ALL=C LANG=C
cmd="$1"
cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"
[ $? != 0 ] && exit 1
case "$cmd" in
*\ /*) exit 0;;
*) printf "%s\n" "error: $cmd" 1>&2; exit 1;;
esac
' _ "$1" || exit 1
}
# Get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp
At least on Mac OS X v10.6.8 (Snow Leopard) using Bash 4.2.24(2) command -v ls does not match a moved /bin/ls-temp.
My setup for a Debian server:
I had the problem when multiple packages contained the same name.
For example apache2. So this was my solution:
function _apt_install() {
apt-get install -y $1 > /dev/null
}
function _apt_install_norecommends() {
apt-get install -y --no-install-recommends $1 > /dev/null
}
function _apt_available() {
if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then
echo "Package is available : $1"
PACKAGE_INSTALL="1"
else
echo "Package $1 is NOT available for install"
echo "We can not continue without this package..."
echo "Exitting now.."
exit 0
fi
}
function _package_install {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install $1
sleep 0.5
fi
fi
}
function _package_install_no_recommends {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install_norecommends $1
sleep 0.5
fi
fi
}
Given an absolute or relative path (in a Unix-like system), I would like to determine the full path of the target after resolving any intermediate symlinks. Bonus points for also resolving ~username notation at the same time.
If the target is a directory, it might be possible to chdir() into the directory and then call getcwd(), but I really want to do this from a shell script rather than writing a C helper. Unfortunately, shells have a tendency to try to hide the existence of symlinks from the user (this is bash on OS X):
$ ls -ld foo bar
drwxr-xr-x 2 greg greg 68 Aug 11 22:36 bar
lrwxr-xr-x 1 greg greg 3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$
What I want is a function resolve() such that when executed from the tmp directory in the above example, resolve("foo") == "/Users/greg/tmp/bar".
readlink -f "$path"
Editor's note: The above works with GNU readlink and FreeBSD/PC-BSD/OpenBSD readlink, but not on OS X as of 10.11.
GNU readlink offers additional, related options, such as -m for resolving a symlink whether or not the ultimate target exists.
Note since GNU coreutils 8.15 (2012-01-06), there is a realpath program available that is less obtuse and more flexible than the above. It's also compatible with the FreeBSD util of the same name. It also includes functionality to generate a relative path between two files.
realpath $path
[Admin addition below from comment by halloleo —danorton]
For Mac OS X (through at least 10.11.x), use readlink without the -f option:
readlink $path
Editor's note: This will not resolve symlinks recursively and thus won't report the ultimate target; e.g., given symlink a that points to b, which in turn points to c, this will only report b (and won't ensure that it is output as an absolute path).
Use the following perl command on OS X to fill the gap of the missing readlink -f functionality:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"
According to the standards, pwd -P should return the path with symlinks resolved.
C function char *getcwd(char *buf, size_t size) from unistd.h should have the same behaviour.
getcwd
pwd
"pwd -P" seems to work if you just want the directory, but if for some reason you want the name of the actual executable I don't think that helps. Here's my solution:
#!/bin/bash
# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")
# resolve symlinks
while [[ -h $SELF_PATH ]]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) get the pwd
# 4) append the basename
DIR=$(dirname -- "$SELF_PATH")
SYM=$(readlink "$SELF_PATH")
SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
One of my favorites is realpath foo
realpath - return the canonicalized absolute pathname
realpath expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path. The resulting path will have no symbolic link, '/./' or
'/../' components.
readlink -e [filepath]
seems to be exactly what you're asking for
- it accepts an arbirary path, resolves all symlinks, and returns the "real" path
- and it's "standard *nix" that likely all systems already have
Another way:
# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; }
# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; }
Putting some of the given solutions together, knowing that readlink is available on most systems, but needs different arguments, this works well for me on OSX and Debian. I'm not sure about BSD systems. Maybe the condition needs to be [[ $OSTYPE != darwin* ]] to exclude -f from OSX only.
#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
Here's how one can get the actual path to the file in MacOS/Unix using an inline Perl script:
FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")
Similarly, to get the directory of a symlinked file:
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
Common shell scripts often have to find their "home" directory even if they are invoked as a symlink. The script thus have to find their "real" position from just $0.
cat `mvn`
on my system prints a script containing the following, which should be a good hint at what you need.
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
Note: I believe this to be a solid, portable, ready-made solution, which is invariably lengthy for that very reason.
Below is a fully POSIX-compliant script / function that is therefore cross-platform (works on macOS too, whose readlink still doesn't support -f as of 10.12 (Sierra)) - it uses only POSIX shell language features and only POSIX-compliant utility calls.
It is a portable implementation of GNU's readlink -e (the stricter version of readlink -f).
You can run the script with sh or source the function in bash, ksh, and zsh:
For instance, inside a script you can use it as follows to get the running's script true directory of origin, with symlinks resolved:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink script / function definition:
The code was adapted with gratitude from this answer.
I've also created a bash-based stand-alone utility version here, which you can install with
npm install rreadlink -g, if you have Node.js installed.
#!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var# -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$#"
A tangent on security:
jarno, in reference to the function ensuring that builtin command is not shadowed by an alias or shell function of the same name, asks in a comment:
What if unalias or unset and [ are set as aliases or shell functions?
The motivation behind rreadlink ensuring that command has its original meaning is to use it to bypass (benign) convenience aliases and functions often used to shadow standard commands in interactive shells, such as redefining ls to include favorite options.
I think it's safe to say that unless you're dealing with an untrusted, malicious environment, worrying about unalias or unset - or, for that matter, while, do, ... - being redefined is not a concern.
There is something that the function must rely on to have its original meaning and behavior - there is no way around that.
That POSIX-like shells allow redefinition of builtins and even language keywords is inherently a security risk (and writing paranoid code is hard in general).
To address your concerns specifically:
The function relies on unalias and unset having their original meaning. Having them redefined as shell functions in a manner that alters their behavior would be a problem; redefinition as an alias is
not necessarily a concern, because quoting (part of) the command name (e.g., \unalias) bypasses aliases.
However, quoting is not an option for shell keywords (while, for, if, do, ...) and while shell keywords do take precedence over shell functions, in bash and zsh aliases have the highest precedence, so to guard against shell-keyword redefinitions you must run unalias with their names (although in non-interactive bash shells (such as scripts) aliases are not expanded by default - only if shopt -s expand_aliases is explicitly called first).
To ensure that unalias - as a builtin - has its original meaning, you must use \unset on it first, which requires that unset have its original meaning:
unset is a shell builtin, so to ensure that it is invoked as such, you'd have to make sure that it itself is not redefined as a function. While you can bypass an alias form with quoting, you cannot bypass a shell-function form - catch 22.
Thus, unless you can rely on unset to have its original meaning, from what I can tell, there is no guaranteed way to defend against all malicious redefinitions.
Is your path a directory, or might it be a file? If it's a directory, it's simple:
(cd "$DIR"; pwd -P)
However, if it might be a file, then this won't work:
DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"
because the symlink might resolve into a relative or full path.
On scripts I need to find the real path, so that I might reference configuration or other scripts installed together with it, I use this:
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
You could set SOURCE to any file path. Basically, for as long as the path is a symlink, it resolves that symlink. The trick is in the last line of the loop. If the resolved symlink is absolute, it will use that as SOURCE. However, if it is relative, it will prepend the DIR for it, which was resolved into a real location by the simple trick I first described.
function realpath {
local r=$1; local t=$(readlink $r)
while [ $t ]; do
r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
t=$(readlink $r)
done
echo $r
}
#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
In case where pwd can't be used (e.g. calling a scripts from a different location), use realpath (with or without dirname):
$(dirname $(realpath $PATH_TO_BE_RESOLVED))
Works both when calling through (multiple) symlink(s) or when directly calling the script - from any location.
This is a symlink resolver in Bash that works whether the link is a directory or a non-directory:
function readlinks {(
set -o errexit -o nounset
declare n=0 limit=1024 link="$1"
# If it's a directory, just skip all this.
if cd "$link" 2>/dev/null
then
pwd -P
return 0
fi
# Resolve until we are out of links (or recurse too deep).
while [[ -L $link ]] && [[ $n -lt $limit ]]
do
cd "$(dirname -- "$link")"
n=$((n + 1))
link="$(readlink -- "${link##*/}")"
done
cd "$(dirname -- "$link")"
if [[ $n -ge $limit ]]
then
echo "Recursion limit ($limit) exceeded." >&2
return 2
fi
printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}
Note that all the cd and set stuff takes place in a subshell.
Try this:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:
The living version lives here:
https://github.com/keen99/shell-functions/tree/master/resolve_path
but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)
Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)
#!/bin/bash
resolve_path() {
#I'm bash only, please!
# usage: resolve_path <a file or directory>
# follows symlinks and relative paths, returns a full real path
#
local owd="$PWD"
#echo "$FUNCNAME for $1" >&2
local opath="$1"
local npath=""
local obase=$(basename "$opath")
local odir=$(dirname "$opath")
if [[ -L "$opath" ]]
then
#it's a link.
#file or directory, we want to cd into it's dir
cd $odir
#then extract where the link points.
npath=$(readlink "$obase")
#have to -L BEFORE we -f, because -f includes -L :(
if [[ -L $npath ]]
then
#the link points to another symlink, so go follow that.
resolve_path "$npath"
#and finish out early, we're done.
return $?
#done
elif [[ -f $npath ]]
#the link points to a file.
then
#get the dir for the new file
nbase=$(basename $npath)
npath=$(dirname $npath)
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -d $npath ]]
then
#the link points to a directory.
cd "$npath"
ndir=$(pwd -P)
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
echo "opath [[ $opath ]]" >&2
echo "npath [[ $npath ]]" >&2
return 1
fi
else
if ! [[ -e "$opath" ]]
then
echo "$FUNCNAME: $opath: No such file or directory" >&2
return 1
#and break early
elif [[ -d "$opath" ]]
then
cd "$opath"
ndir=$(pwd -P)
retval=0
#done
elif [[ -f "$opath" ]]
then
cd $odir
ndir=$(pwd -P)
nbase=$(basename "$opath")
retval=0
#done
else
echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
echo "opath [[ $opath ]]" >&2
return 1
fi
fi
#now assemble our output
echo -n "$ndir"
if [[ "x${nbase:=}" != "x" ]]
then
echo "/$nbase"
else
echo
fi
#now return to where we were
cd "$owd"
return $retval
}
here's a classic example, thanks to brew:
%% ls -l `which mvn`
lrwxr-xr-x 1 draistrick 502 29 Dec 17 10:50 /usr/local/bin/mvn# -> ../Cellar/maven/3.2.3/bin/mvn
use this function and it will return the -real- path:
%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`
%% test.sh
relative symlinked path:
/usr/local/bin/mvn
and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
To work around the Mac incompatibility, I came up with
echo `php -r "echo realpath('foo');"`
Not great but cross OS
Here I present what I believe to be a cross-platform (Linux and macOS at least) solution to the answer that is working well for me currently.
crosspath()
{
local ref="$1"
if [ -x "$(which realpath)" ]; then
path="$(realpath "$ref")"
else
path="$(readlink -f "$ref" 2> /dev/null)"
if [ $? -gt 0 ]; then
if [ -x "$(which readlink)" ]; then
if [ ! -z "$(readlink "$ref")" ]; then
ref="$(readlink "$ref")"
fi
else
echo "realpath and readlink not available. The following may not be the final path." 1>&2
fi
if [ -d "$ref" ]; then
path="$(cd "$ref"; pwd -P)"
else
path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
fi
fi
echo "$path"
}
Here is a macOS (only?) solution. Possibly better suited to the original question.
mac_realpath()
{
local ref="$1"
if [[ ! -z "$(readlink "$ref")" ]]; then
ref="$(readlink "$1")"
fi
if [[ -d "$ref" ]]; then
echo "$(cd "$ref"; pwd -P)"
else
echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
fi
}
My answer here Bash: how to get real path of a symlink?
but in short very handy in scripts:
script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home
These are part of GNU coreutils, suitable for use in Linux systems.
To test everything, we put symlink into /home/test2/, amend some additional things and run/call it from root directory:
/$ /home/test2/symlink
/home/test
Original script home: /home/test
Where
Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
My 2 cents. This function is POSIX compliant, and both the source and the destination can contain ->. However, I have not gotten it work with filenames that container newline or tabs, as ls in general has issues with those.
resolve_symlink() {
test -L "$1" && ls -l "$1" | awk -v SYMLINK="$1" '{ SL=(SYMLINK)" -> "; i=index($0, SL); s=substr($0, i+length(SL)); print s }'
}
I believe the solution here is the file command, with a custom magic file that only outputs the destination of the provided symlink.
This is the best solution, tested in Bash 3.2.57:
# Read a path (similar to `readlink`) recursively, until the physical path without any links (like `cd -P`) is found.
# Accepts any existing path, prints its physical path and exits `0`, exits `1` if some contained links don't exist.
# Motivation: `${BASH_SOURCE[0]}` often contains links; using it directly to extract your project's path may fail.
#
# Example: Safely `source` a file located relative to the current script
#
# source "$(dirname "$(rreadlink "${BASH_SOURCE[0]}")")/relative/script.sh"
#Inspiration: https://stackoverflow.com/a/51089005/6307827
rreadlink () {
declare p="$1" d l
while :; do
d="$(cd -P "$(dirname "$p")" && pwd)" || return $? #absolute path without symlinks
p="$d/$(basename "$p")"
if [ -h "$p" ]; then
l="$(readlink "$p")" || break
#A link must be resolved from its fully resolved parent dir.
d="$(cd "$d" && cd -P "$(dirname "$l")" && pwd)" || return $?
p="$d/$(basename "$l")"
else
break
fi
done
printf '%s\n' "$p"
}