Skip to content

Instantly share code, notes, and snippets.

@geoff-nixon
Last active April 18, 2019 03:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geoff-nixon/1f23957288d371b75a2e to your computer and use it in GitHub Desktop.
Save geoff-nixon/1f23957288d371b75a2e to your computer and use it in GitHub Desktop.
Portable realpath(1) / readlink -f, written is portable POSIX C.
// 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
Copy link

hasufell commented Apr 18, 2019

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