Check if file is a link on Linux - shell

I am trying to check if a symbolic link exists from a KornShell (ksh) script using -h filename command. This works good on HP boxes.
Not sure what is the correct option for Linux, AIX and Solaris boxes.
Any insight regarding this?

-h is part of the POSIX spec; it should work everywhere that is vaguely reasonable.
According to man test on Mac OS X:
-h file True if file exists and is a symbolic link. This operator
is retained for compatibility with previous versions of
this program. Do not rely on its existence; use -L instead.
-L is also standardized, so if you find anywhere that -h does not work, you should try -L instead.

It is -h on linux systems also, at least (bash is my shell):
lrwxrwxrwx 1 mule mule 37 Feb 27 09:43 mule.log -> /home/mule/runtime/mule/logs/mule.log
[mule#emdlcis01 ~]$ if [[ -h mule.log ]]; then echo "mule.log is a symlink that exists" ; fi
mule.log is a symlink that exists
Check man test to see the available operators you have available to use on files and strings in your given enviorment.

The best answer is to try whatever the 'test' man page says on your system. If that seems to work, look no further. However, if it doesn't seem to work, or if you have questions about more obscure options to test, then you should also check the man page for your shell to see if 'expr' or '[' are built-ins. In that case, the shell might be using an internal implementation instead of calling the expr utility from /bin. On Solaris I verified that ksh93 treats [ as a builtin (even though the man page doesn't seem to say so). From the truss output you can see that ksh is not running the expr command for [.
% truss -f -texec /bin/ksh '[ -h /home ]'
26056: execve("/usr/bin/ksh", 0x08047708, 0x08047714) argc = 2
26056: execve("/usr/bin/ksh93", 0x08047708, 0x08047714) argc = 2
26056: execve("/usr/bin/amd64/ksh93", 0x08047704, 0x08047710) argc = 2
% truss -f -texec /bin/ksh '/bin/expr -h /home ]'
26058: execve("/usr/bin/ksh", 0x08047700, 0x0804770C) argc = 2
26058: execve("/usr/bin/ksh93", 0x08047700, 0x0804770C) argc = 2
26058: execve("/usr/bin/amd64/ksh93", 0x080476FC, 0x08047708) argc = 2
26058: execve("/bin/expr", 0x00418360, 0x00418398) argc = 4

Thw two possible options are
if [ -h filename ]
OR
ls -ltr | grep filename | grep ^l
If $? is 0 then file is linked otherwise its not linked, I will prefer the first option instead.

Related

Why do `ls /bin` and `ls -l /bin` print different files

I'm new to shell and still practicing on it.
When I typed
ls /bin
The shell prints many files
'[' mousetweaks
aa-enabled mscompress
aa-exec msexpand
aa-features-abi mt
aconnect mt-gnu
..
But when I typed
ls -l /bin
I was expecting the same output file in long format, but it seems like the shell just gives me a soft link
lrwxrwxrwx 1 root root 7 Sept 5 15:22 /bin -> usr/bin
I can't figure out why.
The POSIX specification for ls says this:
-l(The letter ell.) Do not follow symbolic links named as operands unless the -H or -L options are specified. Write out in long format (see the STDOUT section). Disable the -C, -m, and -x options.
If you want to change the behavior, in addition to the two options specified above, you can also put a trailing slash after the name (i.e., /bin/ instead of /bin).

How to see what /bin/sh points to

I was reading about the differences between /bin/sh and /bin/bash and came across this interesting question/answer: here
I made a comment to the answer asking this same question in my title to which he replied with an updated answer:
How can you find out what /bin/sh points to on your system?
The complication is that /bin/sh could be a symbolic link or a hard
link. If it's a symbolic link, a portable way to resolve it is:
> % file -h /bin/sh
> /bin/sh: symbolic link to bash
If it's a hard link,
try
> % find -L /bin -samefile /bin/sh
> /bin/sh
> /bin/bash
I tried this and had trouble so I thought I would make a separate question.
My results from the linked answer:
>file -h /bin/sh
>/bin/sh: executable (RISC System/6000) or object module
>find -L /bin -samefile /bin/sh
>find: bad option -samefile
What am I missing?
I'm running AIX:
>oslevel -s
7100-03-03-1415
If you need to programatically test if they are the same, you can use stat to query the inode of /bin/sh and compare with the inode of /bin/bash.
if [ $(stat -L -c %i /bin/sh) -eq $(stat -L -c %i /bin/bash) ]; then
.....
fi
If you just need to see with your eyes if they are the same run the stat commands and see if they return the same inode number.
stat -L -c %i /bin/sh
stat -L -c %i /bin/bash
Since you are only searching through bin anyway, you can bypass find entirely and just check if sh and bash are hard links to the same file:
test /bin/sh -ef /bin/bash
OR
[ /bin/sh -ef /bin/bash ]
This is not as reliable as running find on all the possibilities, but it's a good start. While AIX find doesn't support -samefile, it does support -exec, which can be combined with the command above to simulate the same functionality:
find -L /bin -exec test /bin/sh -ef '{}' ';'
Check for GNU Bash
I'm going to answer your question in a different way, because it's actually simpler to find out if sh is GNU Bash (or something else that responds to a --version flag) than it is to chase inodes. There's also the edge case where the shell is renamed rather than linked, in which case mapping links won't really help you find an answer.
For example, to interrogate /bin/sh on macOS:
$ /bin/sh --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)
Copyright (C) 2007 Free Software Foundation, Inc.
Alternatively, you can grep (or similar) for the string bash to capture the exit status. For example:
# Close stderr in case sh doesn't have a version flag.
if sh --version 2>&- | grep -qF bash; then
echo "sh is bash"
else
echo "sh isn't bash"
fi
Of course, /bin/sh could be some other shell besides bash or the original bourne shell, but that's outside the scope of your original question. However, many shells such as ksh and tcsh also support the version flag, so judicious use of a case statement could help you extend the test to determine exactly which shell binary /bin/sh really is.
As the answer you commented on says, -samefile isn't a standard feature of find. Neither is -inum, which searches based on an explicitly given inode number. But if your find supports that, use it.
POSIX ls supports -i, which prints the inode numbers. If find doesn't have -inum, all you need to do is go through all plausible files /bin/sh could be a hard link with...
Though before that, you could check if the file even has any other hard links, ls -l is required to show the number of links:
$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1029624 Nov 5 23:22 /bin/bash
^ here
Of course, there's no requirement for /bin/sh to be linked to anything. It could be just another program. (Or even an identical copy of some of other file, though I don't think that's very likely.)
You can just use :
ls -l /bin/sh
You can also do readlink -f /bin/sh. For me it points to /bin/dash.
I can not really see the point of this exercise.
/bin/sh is a POSIX shell and bash has extensions to the POSIX standard that /bin/sh does not support.
If you are writing a POSIX shell script, use /bin/sh. If you are writing a bash shell script, use bash.
Do not try to write a bash script that will be executed with /bin/sh, and don't try to programatically try to determine the abilities of the current shell based on what /bin/sh (or the current interpreter) is linked to. Instead, make the script executable and use the correct #!-line for the script.

Relative paths based on file location instead of current working directory [duplicate]

This question already has answers here:
How do I get the directory where a Bash script is located from within the script itself?
(74 answers)
Closed 8 years ago.
Given:
some.txt
dir
|-cat.sh
With cat.sh having the content:
cat ../some.txt
Then running ./cat.sh inside dir works fine while running ./dir/cat.sh on the same level as dir does not. I expect this to be due to the different working directories. Is there an easy way to make the path ../some.txt relative to the location of cat.sh?
What you want to do is get the absolute path of the script (available via ${BASH_SOURCE[0]}) and then use this to get the parent directory and cd to it at the beginning of the script.
#!/bin/bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$parent_path"
cat ../some.text
This will make your shell script work independent of where you invoke it from. Each time you run it, it will be as if you were running ./cat.sh inside dir.
Note that this script only works if you're invoking the script directly (i.e. not via a symlink), otherwise the finding the current location of the script gets a little more tricky)
#Martin Konecny's answer provides an effective solution, but - as he mentions - it only works if the actual script is not invoked through a symlink residing in a different directory.
This answer covers that case: a solution that also works when the script is invoked through a symlink or even a chain of symlinks:
Linux / GNU readlink solution:
If your script needs to run on Linux only or you know that GNU readlink is in the $PATH, use readlink -f, which conveniently resolves a symlink to its ultimate target:
scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
Note that GNU readlink has 3 related options for resolving a symlink to its ultimate target's full path: -f (--canonicalize), -e (--canonicalize-existing), and -m (--canonicalize-missing) - see man readlink.
Since the target by definition exists in this scenario, any of the 3 options can be used; I've chosen -f here, because it is the most well-known one.
Multi-(Unix-like-)platform solution (including platforms with a POSIX-only set of utilities):
If your script must run on any platform that:
has a readlink utility, but lacks the -f option (in the GNU sense of resolving a symlink to its ultimate target) - e.g., macOS.
macOS uses an older version of the BSD implementation of readlink; note that recent versions of FreeBSD/PC-BSD do support -f.
does not even have readlink, but has POSIX-compatible utilities - e.g., HP-UX (thanks, #Charles Duffy).
The following solution, inspired by https://stackoverflow.com/a/1116890/45375,
defines helper shell function, rreadlink(), which resolves a given symlink to its ultimate target in a loop - this function is in effect a POSIX-compliant implementation of GNU readlink's -e option, which is similar to the -f option, except that the ultimate target must exist.
Note: The function is a bash function, and is POSIX-compliant only in the sense that only POSIX utilities with POSIX-compliant options are used. For a version of this function that is itself written in POSIX-compliant shell code (for /bin/sh), see here.
If readlink is available, it is used (without options) - true on most modern platforms.
Otherwise, the output from ls -l is parsed, which is the only POSIX-compliant way to determine a symlink's target.
Caveat: this will break if a filename or path contains the literal substring -> - which is unlikely, however.
(Note that platforms that lack readlink may still provide other, non-POSIX methods for resolving a symlink; e.g., #Charles Duffy mentions HP-UX's find utility supporting the %l format char. with its -printf primary; in the interest of brevity the function does NOT try to detect such cases.)
An installable utility (script) form of the function below (with additional functionality) can be found as rreadlink in the npm registry; on Linux and macOS, install it with [sudo] npm install -g rreadlink; on other platforms (assuming they have bash), follow the manual installation instructions.
If the argument is a symlink, the ultimate target's canonical path is returned; otherwise, the argument's own canonical path is returned.
#!/usr/bin/env bash
# Helper function.
rreadlink() ( # execute function in a *subshell* to localize the effect of `cd`, ...
local target=$1 fname targetDir readlinkexe=$(command -v readlink) CDPATH=
# Since we'll be using `command` below for a predictable execution
# environment, we make sure that it has its original meaning.
{ \unalias command; \unset -f command; } &>/dev/null
while :; do # Resolve potential symlinks until the ultimate target is found.
[[ -L $target || -e $target ]] || { command printf '%s\n' "$FUNCNAME: 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 is defined
# relative to the symlink's own directory.
if [[ -n $readlinkexe ]]; then # Use `readlink`.
target=$("$readlinkexe" -- "$fname")
else # `readlink` utility not available.
# Parse `ls -l` output, which, unfortunately, is the only POSIX-compliant
# way to determine a symlink's target. Hypothetically, this can break with
# filenames containig literal ' -> ' and embedded newlines.
target=$(command ls -l -- "$fname")
target=${target#* -> }
fi
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
)
# Determine ultimate script dir. using the helper function.
# Note that the helper function returns a canonical path.
scriptDir=$(dirname -- "$(rreadlink "$BASH_SOURCE")")
Just one line will be OK.
cat "`dirname $0`"/../some.txt

How to find out where alias (in the bash sense) is defined when running Terminal in Mac OS X

How can I find out where an alias is defined on my system? I am referring to the kind of alias that is used within a Terminal session launched from Mac OS X (10.6.3).
For example, if I enter the alias command with no parameters at a Terminal command prompt, I get a list of aliases that I have set, for example:
alias mysql='/usr/local/mysql/bin/mysql'
However, I have searched all over my system using Spotlight and mdfind in various startup files and so far can not find where this alias has been defined. ( I did it a long time ago and didn't write down where I assigned the alias).
For OSX, this 2-step sequence worked well for me, in locating an alias I'd created long ago and couldn't locate in expected place (~/.zshrc).
cweekly:~ $ which la
la: aliased to ls -lAh
cweekly:~$ grep -r ' ls -lAh' ~
/Users/cweekly//.oh-my-zsh/lib/aliases.zsh:alias la='ls -lAh'
Aha! "Hiding" in ~/.oh-my-zsh/lib/aliases.zsh. I had poked around a bit in .oh-my-zsh but had overlooked lib/aliases.zsh.
you can just simply type in alias on the command prompt to see what aliases you have. Otherwise, you can do a find on the most common places where aliases are defined, eg
grep -RHi "alias" /etc /root
First use the following commands
List all functions
functions
List all aliases
alias
If you aren't finding the alias or function consider a more aggressive searching method
Bash version
bash -ixlc : 2>&1 | grep thingToSearchHere
Zsh version
zsh -ixc : 2>&1 | grep thingToSearchHere
Brief Explanation of Options
-i Force shell to be interactive.
-c Take the first argument as a command to execute
-x -- equivalent to --xtrace
-l Make bash act as if invoked as a login shell
Also in future these are the standard bash config files
/etc/profile
~/.bash_profile or ~/.bash_login or ~/.profile
~/.bash_logout
~/.bashrc
More info: http://www.heimhardt.com/htdocs/bashrcs.html
A bit late to the party, but I was having the same problem (trying to find where the "l." command was aliased in RHEL6), and ended up in a place not mentioned in the previous answers. It may not be found in all bash implementations, but if the /etc/profile.d/ directory exists, try grepping there for unexplained aliases. That's where I found:
[user#server ~]$ grep l\\. /etc/profile.d/*
/etc/profile.d/colorls.csh:alias l. 'ls -d .*'
/etc/profile.d/colorls.csh:alias l. 'ls -d .* --color=auto'
/etc/profile.d/colorls.sh: alias l.='ls -d .*' 2>/dev/null
/etc/profile.d/colorls.sh:alias l.='ls -d .* --color=auto' 2>/dev/null
The directory isn't mentioned in the bash manpage, and isn't properly part of where bash searches for profile/startup info, but in the case of RHEL you can see the calling code within /etc/profile:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null 2>&1
fi
fi
done
Please do check custom installations/addons/plugins you have added, in addition to the .zshrc/.bashrc/.profile etc files
So for me: it was git aliased to 'g'.
$ which g
g: aliased to git
Then I ran the following command to list all aliases
$ alias
I found a whole lot of git related aliases that I knew I had not manually added.
This got me thinking about packages or configurations I had installed. And so went to the
.oh-my-zsh
directory. Here I ran the following command:
$ grep -r 'git' . |grep -i alias
And lo and behold, I found my alias in :
./plugins/git/git.plugin.zsh
I found the answer ( I had been staring at the correct file but missed the obvious ).
The aliases in my case are defined in the file ~/.bash_profile
Somehow this eluded me.
For more complex setups (e.g. when you're using a shell script framework like bash-it, oh-my-zsh or the likes) it's often useful to add 'alias mysql' at key positions in your scripts. This will help you figure out exactly when the alias is added.
e.g.:
echo "before sourcing .bash-it:"
alias mysql
. $HOME/.bash-it/bash-it.sh
echo "after sourcing bash:"
alias mysql
I think that maybe this is similar to what ghostdog74 meant however their command didn't work for me.
I would try something like this:
for i in `find . -type f`; do # find all files in/under current dir
echo "========"
echo $i # print file name
cat $i | grep "alias" # find if it has alias and if it does print the line containing it
done
If you wanted to be really fancy you could even add an if [[ grep -c "alias" ]] then <print file name>
The only reliable way of finding where the alias could have been defined is by analyzing the list of files opened by bash using dtruss.
If
$ csrutil status
System Integrity Protection status: enabled.
you won't be able to open bash and you may need a copy.
$ cp /bin/bash mybash
$ $ codesign --remove-signature mybash
and then use
sudo dtruss -t open ./mybash -ic exit 2>&1 | awk -F'"' '/^open/ {print substr($2, 0, length($2)-2)}'
to list all the files where the alias could have been defined, like
/dev/dtracehelper
/dev/tty
/usr/share/locale/en_CA.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en_CA/LC_MESSAGES/BASH.mo
/usr/share/locale/en.UTF-8/LC_MESSAGES/BASH.mo
/usr/share/locale/en.utf8/LC_MESSAGES/BASH.mo
/usr/share/locale/en/LC_MESSAGES/BASH.mo
/Users/user/.bashrc
/Users/user/.bash_aliases
/Users/user/.bash_history
...
Try: alias | grep name_of_alias
Ex.: alias | grep mysql
or, as already mentioned above
which name_of_alias
In my case, I use Oh My Zsh, so I put aliases definition in ~/.zshrc file.

How can I get the behavior of GNU's readlink -f on a Mac? [closed]

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

Resources