Bash script absolute path with OS X

后端 未结 15 987
误落风尘
误落风尘 2020-11-29 18:20

I am trying to obtain the absolute path to the currently running script on OS X.

I saw many replies going for readlink -f $0. However since OS X\'s

相关标签:
15条回答
  • 2020-11-29 18:58

    Since there is a realpath as others have pointed out:

    // realpath.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int main (int argc, char* argv[])
    {
      if (argc > 1) {
        for (int argIter = 1; argIter < argc; ++argIter) {
          char *resolved_path_buffer = NULL;
          char *result = realpath(argv[argIter], resolved_path_buffer);
    
          puts(result);
    
          if (result != NULL) {
            free(result);
          }
        }
      }
    
      return 0;
    }
    

    Makefile:

    #Makefile
    OBJ = realpath.o
    
    %.o: %.c
          $(CC) -c -o $@ $< $(CFLAGS)
    
    realpath: $(OBJ)
          gcc -o $@ $^ $(CFLAGS)
    

    Then compile with make and put in a soft link with:
    ln -s $(pwd)/realpath /usr/local/bin/realpath

    0 讨论(0)
  • 2020-11-29 19:00

    I needed a realpath replacement on OS X, one that operates correctly on paths with symlinks and parent references just like readlink -f would. This includes resolving symlinks in the path before resolving parent references; e.g. if you have installed the homebrew coreutils bottle, then run:

    $ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
    $ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
    /private/var/log
    

    Note that readlink -f has resolved /tmp/linkeddir before resolving the .. parent dir reference. Of course, there is no readlink -f on Mac either.

    So as part of the a bash implementation for realpath I re-implemented what a GNUlib canonicalize_filename_mode(path, CAN_ALL_BUT_LAST) function call does, in Bash 3.2; this is also the function call that GNU readlink -f makes:

    # shellcheck shell=bash
    set -euo pipefail
    
    _contains() {
        # return true if first argument is present in the other arguments
        local elem value
    
        value="$1"
        shift
    
        for elem in "$@"; do 
            if [[ $elem == "$value" ]]; then
                return 0
            fi
        done
        return 1
    }
    
    _canonicalize_filename_mode() {
        # resolve any symlink targets, GNU readlink -f style
        # where every path component except the last should exist and is
        # resolved if it is a symlink. This is essentially a re-implementation
        # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
        # takes the path to canonicalize as first argument
    
        local path result component seen
        seen=()
        path="$1"
        result="/"
        if [[ $path != /* ]]; then  # add in current working dir if relative
            result="$PWD"
        fi
        while [[ -n $path ]]; do
            component="${path%%/*}"
            case "$component" in
                '') # empty because it started with /
                    path="${path:1}" ;;
                .)  # ./ current directory, do nothing
                    path="${path:1}" ;;
                ..) # ../ parent directory
                    if [[ $result != "/" ]]; then  # not at the root?
                        result="${result%/*}"      # then remove one element from the path
                    fi
                    path="${path:2}" ;;
                *)
                    # add this component to the result, remove from path
                    if [[ $result != */ ]]; then
                        result="$result/"
                    fi
                    result="$result$component"
                    path="${path:${#component}}"
                    # element must exist, unless this is the final component
                    if [[ $path =~ [^/] && ! -e $result ]]; then
                        echo "$1: No such file or directory" >&2
                        return 1
                    fi
                    # if the result is a link, prefix it to the path, to continue resolving
                    if [[ -L $result ]]; then
                        if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                            # we've seen this link before, abort
                            echo "$1: Too many levels of symbolic links" >&2
                            return 1
                        fi
                        seen+=("$result")
                        path="$(readlink "$result")$path"
                        if [[ $path = /* ]]; then
                            # if the link is absolute, restart the result from /
                            result="/"
                        elif [[ $result != "/" ]]; then
                            # otherwise remove the basename of the link from the result
                            result="${result%/*}"
                        fi
                    elif [[ $path =~ [^/] && ! -d $result ]]; then
                        # otherwise all but the last element must be a dir
                        echo "$1: Not a directory" >&2
                        return 1
                    fi
                    ;;
            esac
        done
        echo "$result"
    }
    

    It includes circular symlink detection, exiting if the same (intermediary) path is seen twice.

    If all you need is readlink -f, then you can use the above as:

    readlink() {
        if [[ $1 != -f ]]; then  # poor-man's option parsing
            # delegate to the standard readlink command
            command readlink "$@"
            return
        fi
    
        local path result seenerr
        shift
        seenerr=
        for path in "$@"; do
            # by default readlink suppresses error messages
            if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
                seenerr=1
                continue
            fi
            echo "$result"
        done
        if [[ $seenerr ]]; then
            return 1;
        fi
    }
    

    For realpath, I also needed --relative-to and --relative-base support, which give you relative paths after canonicalizing:

    _realpath() {
        # GNU realpath replacement for bash 3.2 (OS X)
        # accepts --relative-to= and --relative-base options
        # and produces canonical (relative or absolute) paths for each
        # argument on stdout, errors on stderr, and returns 0 on success
        # and 1 if at least 1 path triggered an error.
    
        local relative_to relative_base seenerr path
    
        relative_to=
        relative_base=
        seenerr=
    
        while [[ $# -gt 0 ]]; do
            case $1 in
                "--relative-to="*)
                    relative_to=$(_canonicalize_filename_mode "${1#*=}")
                    shift 1;;
                "--relative-base="*)
                    relative_base=$(_canonicalize_filename_mode "${1#*=}")
                    shift 1;;
                *)
                    break;;
            esac
        done
    
        if [[
            -n $relative_to
            && -n $relative_base
            && ${relative_to#${relative_base}/} == "$relative_to"
        ]]; then
            # relative_to is not a subdir of relative_base -> ignore both
            relative_to=
            relative_base=
        elif [[ -z $relative_to && -n $relative_base ]]; then
            # if relative_to has not been set but relative_base has, then
            # set relative_to from relative_base, simplifies logic later on
            relative_to="$relative_base"
        fi
    
        for path in "$@"; do
            if ! real=$(_canonicalize_filename_mode "$path"); then
                seenerr=1
                continue
            fi
    
            # make path relative if so required
            if [[
                -n $relative_to
                && ( # path must not be outside relative_base to be made relative
                    -z $relative_base || ${real#${relative_base}/} != "$real"
                )
            ]]; then
                local common_part parentrefs
    
                common_part="$relative_to"
                parentrefs=
                while [[ ${real#${common_part}/} == "$real" ]]; do
                    common_part="$(dirname "$common_part")"
                    parentrefs="..${parentrefs:+/$parentrefs}"
                done
    
                if [[ $common_part != "/" ]]; then
                    real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
                fi
            fi
    
            echo "$real"
        done
        if [[ $seenerr ]]; then
            return 1
        fi
    }
    
    if ! command -v realpath > /dev/null 2>&1; then
        # realpath is not available on OSX unless you install the `coreutils` brew
        realpath() { _realpath "$@"; }
    fi
    

    I included unit tests in my Code Review request for this code.

    0 讨论(0)
  • 2020-11-29 19:02
    abs_path () {    
       echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
    }
    

    dirname will give the directory name of /path/to/file, i.e. /path/to.

    cd /path/to; pwd ensures that the path is absolute.

    basename will give just the filename in /path/to/file, i.e.file.

    0 讨论(0)
提交回复
热议问题