Last active
April 18, 2019 03:47
-
-
Save geoff-nixon/1f23957288d371b75a2e to your computer and use it in GitHub Desktop.
Portable realpath(1) / readlink -f, written is portable POSIX C.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// So, this used to be a really terrible shell script I wrote years ago. | |
// Its was buggy in all kinds of corner cases, If you really need it, check | |
// out the revision history. Otherwise, if you have a functioning C compiler, | |
// you *really* should be using the system's realpath(3) function to do this. | |
// Here's a bare-bones version. To compile, just: `cc realpath.c -o realpath` | |
#include <stdio.h> | |
#include <stdlib.h> | |
int main(int argc, char *argv[]) { | |
char *symlinkpath = argv[1]; | |
char *actualpath = realpath(symlinkpath, NULL); | |
if (actualpath != NULL) { | |
realpath(symlinkpath, actualpath); | |
printf("%s", actualpath); | |
free(actualpath); | |
return 0; | |
} else { | |
return 1; | |
} | |
} |
@hasufell Yeah... that script was pretty terrible. You're quite right about parsing the output of ls
, but you can do something similar safely, using cut
on the output of stat -F
.
I do like your script, though it still gets caught on corner cases like symlink loops and nonexistent relative paths. Its also a bit inefficient: I think you could do away with much of that loop if you just used pwd -P
?
I've decided to take mine down; if someone wants to resurrect it, they can go through the history. :)
I do like your script, though it still gets caught on corner cases like symlink loops and nonexistent relative paths.
--- script
+++ script
@@ -12,24 +12,30 @@
# @RETURNS: 0 on success, 1 otherwise (e.g. internal error)
posix_realpath() {
[ -z "$1" ] && return 1
+ current_loop=0
+ max_loops=50
mysource=$1
while [ -h "${mysource}" ]; do
+ current_loop=$((current_loop+1))
mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )"
mysource="$(readlink "${mysource}")"
[ "${mysource%${mysource#?}}"x != '/x' ] && mysource="${mydir}/${mysource}"
+
+ if [ ${current_loop} -gt ${max_loops} ] ; then
+ (>&2 echo "${1}: Too many levels of symbolic links")
+ break
+ fi
done
mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )"
if [ -z "${mydir}" ] ; then
(>&2 echo "${1}: Permission denied")
- elif [ ! -e "$1" ] ; then
- echo "${mysource}"
else
echo "${mydir%/}/$(basename "${mysource}")"
fi
- unset mysource mydir posix_realpath_error
+ unset current_loop max_loops mysource mydir
}
posix_realpath "$@"
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you use shellcheck you will see a lot of warnings in this script (especially the use of
$@
).Additionally
$(ls -ld "$@" | sed 's|.* -> ||')
really makes this script unreliable, imo.ls
is not meant to be used programmatically. The output is for humans, see https://github.com/koalaman/shellcheck/wiki/SC2012This is a better solution for
realpath
imo:The error handling is not 100% the same though. E.g. "directory does not exist" vs "cannot enter directory".