Unix shell script find out which directory the script file resides?

前端 未结 16 2011
我寻月下人不归
我寻月下人不归 2020-12-02 03:14

Basically I need to run the script with paths related to the shell script file location, how can I change the current directory to the same directory as where the script fil

相关标签:
16条回答
  • 2020-12-02 03:58
    BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
    echo "BASE_DIR => $BASE_DIR"
    
    0 讨论(0)
  • 2020-12-02 04:00

    That should do the trick:

    echo `pwd`/`dirname $0`
    

    It might look ugly depending on how it was invoked and the cwd but should get you where you need to go (or you can tweak the string if you care how it looks).

    0 讨论(0)
  • 2020-12-02 04:02

    INTRODUCTION

    This answer corrects the very broken but shockingly top voted answer of this thread (written by TheMarko):

    #!/usr/bin/env bash
    
    BASEDIR=$(dirname "$0")
    echo "$BASEDIR"
    

    WHY DOES USING dirname "$0" ON IT'S OWN NOT WORK?

    dirname $0 will only work if user launches script in a very specific way. I was able to find several situations where this answer fails and crashes the script.

    First of all, let's understand how this answer works. He's getting the script directory by doing

    dirname "$0"
    

    $0 represents the first part of the command calling the script (it's basically the inputted command without the arguments:

    /some/path/./script argument1 argument2
    

    $0="/some/path/./script"

    dirname basically finds the last / in a string and truncates it there. So if you do:

      dirname /usr/bin/sha256sum
    

    you'll get: /usr/bin

    This example works well because /usr/bin/sha256sum is a properly formatted path but

      dirname "/some/path/./script"
    

    wouldn't work well and would give you:

      BASENAME="/some/path/." #which would crash your script if you try to use it as a path
    

    Say you're in the same dir as your script and you launch it with this command

    ./script   
    

    $0 in this situation will be ./script and dirname $0 will give:

    . #or BASEDIR=".", again this will crash your script
    

    Using:

    sh script
    

    Without inputting the full path will also give a BASEDIR="."

    Using relative directories:

     ../some/path/./script
    

    Gives a dirname $0 of:

     ../some/path/.
    

    If you're in the /some directory and you call the script in this manner (note the absence of / in the beginning, again a relative path):

     path/./script.sh
    

    You'll get this value for dirname $0:

     path/. 
    

    and ./path/./script (another form of the relative path) gives:

     ./path/.
    

    The only two situations where basedir $0 will work is if the user use sh or touch to launch a script because both will result in $0:

     $0=/some/path/script
    

    which will give you a path you can use with dirname.

    THE SOLUTION

    You'd have account for and detect every one of the above mentioned situations and apply a fix for it if it arises:

    #!/bin/bash
    #this script will only work in bash, make sure it's installed on your system.
    
    #set to false to not see all the echos
    debug=true
    
    if [ "$debug" = true ]; then echo "\$0=$0";fi
    
    
    #The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
    BASEDIR=$(dirname "$0") #3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)
                                                                         #situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)
                                                                         #situation3: different dir but relative path used to launch script
    if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 
    
    if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1
    
    _B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
    if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
    if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
            if [ "$B_2" = "./" ]; then
                    #covers ./relative_path/(./)script
                    if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
            else
                    #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                    if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
            fi
    fi
    
    if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
    
    0 讨论(0)
  • 2020-12-02 04:03

    If you want to get the actual script directory (irrespective of whether you are invoking the script using a symlink or directly), try:

    BASEDIR=$(dirname $(realpath "$0"))
    echo "$BASEDIR"
    

    This works on both linux and macOS. I couldn't see anyone here mention about realpath. Not sure whether there are any drawbacks in this approach.

    on macOS, you need to install coreutils to use realpath. Eg: brew install coreutils.

    0 讨论(0)
  • 2020-12-02 04:03

    Let's make it a POSIX oneliner:

    a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)
    

    Tested on many Bourne-compatible shells including the BSD ones.

    As far as I know I am the author and I put it into public domain. For more info see: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/

    0 讨论(0)
  • 2020-12-02 04:03

    So many answers, all plausible, each with pro's and con's & slightly differeing objectives (which should probably be stated for each). Here's another solution that meets a primary objective of both being clear and working across all systems, on all bash (no assumptions about bash versions, or readlink or pwd options), and reasonably does what you'd expect to happen (eg, resolving symlinks is an interesting problem, but isn't usually what you actually want), handle edge cases like spaces in paths, etc., ignores any errors and uses a sane default if there are any issues.

    Each component is stored in a separate variable that you can use individually:

    # script path, filename, directory
    PROG_PATH=${BASH_SOURCE[0]}      # this script's name
    PROG_NAME=${PROG_PATH##*/}       # basename of script (strip path)
    PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
    
    0 讨论(0)
提交回复
热议问题