Bash script absolute path with OS X

后端 未结 15 988
误落风尘
误落风尘 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:49

    A more command-line-friendly variant of the Python solution:

    python -c "import os; print(os.path.realpath('$1'))"
    
    0 讨论(0)
  • 2020-11-29 18:49

    On macOS, the only solution that I've found to this that reliably handles symlinks is by using realpath. Since this requires brew install coreutils, I just automated that step. My implementation looks like this:

    #!/usr/bin/env bash
    
    set -e
    
    if ! which realpath >&/dev/null; then
      if ! which brew >&/dev/null; then
        msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
        echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
        exit 1
      fi
      echo "Installing coreutils/realpath"
      brew install coreutils >&/dev/null
    fi
    
    thisDir=$( dirname "`realpath "$0"`" )
    echo "This script is run from \"$thisDir\""
    


    This errors if they don't have brew installed, but you could alternatively just install that too. I just didn't feel comfortable automating something that curls arbitrary ruby code from the net.

    Note that this an automated variation on Oleg Mikheev's answer.


    One important test

    One good test of any of these solutions is:

    1. put the code in a script file somewhere
    2. in another directory, symlink (ln -s) to that file
    3. run the script from that symlink

    Does the solution dereference the symlink, and give you the original directory? If so, it works.

    0 讨论(0)
  • 2020-11-29 18:52

    This seems to work for OSX, doesnt require any binaries, and was pulled from here

    function normpath() {
      # Remove all /./ sequences.
      local path=${1//\/.\//\/}
    
      # Remove dir/.. sequences.
      while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
        path=${path/${BASH_REMATCH[0]}/}
      done
      echo $path
    }
    
    0 讨论(0)
  • 2020-11-29 18:54

    There's a realpath() C function that'll do the job, but I'm not seeing anything available on the command-line. Here's a quick and dirty replacement:

    #!/bin/bash
    
    realpath() {
        [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
    }
    
    realpath "$0"
    

    This prints the path verbatim if it begins with a /. If not it must be a relative path, so it prepends $PWD to the front. The #./ part strips off ./ from the front of $1.

    0 讨论(0)
  • 2020-11-29 18:56

    So as you can see above, I took a shot at this about 6 months ago. I totally forgot about it until I found myself in need of a similar thing again. I was completely shocked to see just how rudimentary it was; I've been teaching myself to code pretty intensively for about a year now, but I often feel like maybe I haven't learned anything at all when things are at their worst.

    I would remove the 'solution' above, but I really like it sort of being a record of of how much I really have learnt over the past few months.

    But I digress. I sat down and worked it all out last night. The explanation in the comments should be sufficient. If you want to track the copy I'm continuing to work on, you can follow this gist. This probably does what you need.

    #!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.
    
    ## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
    ## dereference symbolic links (ala 'readlink') until the originating file
    ## is found. This is effectively the same function provided in stdlib.h as
    ## 'realpath' and on the command line in GNU 'readlink -f'.
    
    ## Neither of these tools, however, are particularly accessible on the many
    ## systems that do not have the GNU implementation of readlink, nor ship
    ## with a system compiler (not to mention the requisite knowledge of C).
    
    ## This script is written with portability and (to the extent possible, speed)
    ## in mind, hence the use of printf for echo and case statements where they
    ## can be substituded for test, though I've had to scale back a bit on that.
    
    ## It is (to the best of my knowledge) written in standard POSIX shell, and
    ## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
    ## issues with it, though I'm not sure why; so probably best to avoid for now.
    
    ## Particularly useful (in fact, the reason I wrote this) is the fact that
    ## it can be used within a shell script to find the path of the script itself.
    ## (I am sure the shell knows this already; but most likely for the sake of
    ## security it is not made readily available. The implementation of "$0"
    ## specificies that the $0 must be the location of **last** symbolic link in
    ## a chain, or wherever it resides in the path.) This can be used for some
    ## ...interesting things, like self-duplicating and self-modifiying scripts.
    
    ## Currently supported are three errors: whether the file specified exists
    ## (ala ENOENT), whether its target exists/is accessible; and the special
    ## case of when a sybolic link references itself "foo -> foo": a common error
    ## for beginners, since 'ln' does not produce an error if the order of link
    ## and target are reversed on the command line. (See POSIX signal ELOOP.)
    
    ## It would probably be rather simple to write to use this as a basis for
    ## a pure shell implementation of the 'symlinks' util included with Linux.
    
    ## As an aside, the amount of code below **completely** belies the amount
    ## effort it took to get this right -- but I guess that's coding for you.
    
    ##===-------------------------------------------------------------------===##
    
    for argv; do :; done # Last parameter on command line, for options parsing.
    
    ## Error messages. Use functions so that we can sub in when the error occurs.
    
    recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
    dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
    errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.
    
    # Probably best not to install as 'pathfull', if you can avoid it.
    
    pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"
    
    ## 'test and 'ls' report different status for bad symlinks, so we use this.
    
     if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
        errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
        recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
        dangling 1>&2; exit 1; fi
     fi
    
    ## Not a link, but there might be one in the path, so 'cd' and 'pwd'.
    
     if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
       printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
     fi
    
    ## Walk the symlinks back to the origin. Calls itself recursivly as needed.
    
     while [ "$link" ]; do
       cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
       case "$newlink" in
        "$link") dangling 1>&2 && exit 1                                       ;;
             '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
              *) link="$newlink" && pathfull "$link"                           ;;
       esac
     done
     printf "$(pwd)/$(basename "$newlink")\n"
    }
    
    ## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
    ## else, symlink again (maybe with a different name) elsewhere, and link
    ## back into the directory you started in (or something.) The absolute path
    ## of the script will always be reported in the usage, along with "$0".
    
    if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"
    
    # Yay ANSI l33t codes! Fancy.
     printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
     printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
     printf "Recursive readlink for the authoritative file, symlink after "
     printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
     printf " From within an invocation of a script, locate the script's "
     printf "own file\n         (no matter where it has been linked or "
     printf "from where it is being called).\n\n"
    
    else pathfull "$@"
    fi
    
    0 讨论(0)
  • 2020-11-29 18:57

    I like this:

    #!/usr/bin/env bash
    function realpath() {
        local _X="$PWD"
        local _LNK=$1
        cd "$(dirname "$_LNK")"
        if [ -h "$_LNK" ]; then
            _LNK="$(readlink "$_LNK")"
            cd "$(dirname "$_LNK")"
        fi
        echo "$PWD/$(basename "$_LNK")"
        cd "$_X"
    }
    
    0 讨论(0)
提交回复
热议问题