What Is the Correct Way to Save the Current Directory to a Variable?

When writing fustigate scripts, you might want to get the directory that contains your script. There are multiple ways to accomplish that. Due to the flexibility of bash, some solutions piece of work in some cases, but not in others.

In this post, I evolve from a naive solution to a robust and consequent solution for this common trouble. Spoiler – a "good enough" center ground that I often use is "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )", as long as I know that symbolic links are out of the game.

First attempt: employ $0

Every bit in many other programming languages, information technology is possible to access the command line and arguments. In bash, $0 stores the first element of the executed command:

itamar@legolas ~ $ cat foo.sh echo "$0" itamar@legolas ~ $ ./foo.sh ./foo.sh        

Given that, a valid strategy to get the script directory may be something like:

  1. Go directory component of $0 (using dirname).
  2. Modify directory into #1 (using cd).
  3. Get full path of current directory (using pwd).

This works fine, both when running from the same directory, or from another directory:

itamar@legolas ~ $ cat foo.sh echo "$0" SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )" echo "$SCRIPT_DIR" itamar@legolas ~ $ ./foo.sh ./foo.sh /Users/itamar itamar@legolas ~ $ cd foobar/ itamar@legolas foobar $ ./../foo.sh ./../foo.sh /Users/itamar        

The $( ... ) is used to execute the commands in a subshell and capture the output.

Failure of beginning attempt

Note that $0 stores the first chemical element of the executed command. In the case above, that element was the path to the script, but this isn't always the case. Evidently, when this isn't the case, the approach will fail.

One example of such failure is with sourced scripts:

itamar@legolas foobar $ source ../foo.sh -bash dirname: illegal option -- b usage: dirname path /Users/itamar/foobar        

When sourcing a script, $0 contains -bash. This makes the cd command fail, and pwd render the current directory instead of the script directory.

Time to move on to some other approach.

Second attempt: use $BASH_SOURCE

BASH_SOURCE is an assortment variable. From the fustigate documentation:

An array variable whose members are the source filenames where the corresponding shell role names in the FUNCNAME array variable are divers. The shell office ${FUNCNAME[$i]} is defined in the file ${BASH_SOURCE[$i]} and chosen from ${BASH_SOURCE[$i+1]}

I don't know almost you, only this definition isn't very clear to me. I desire to run across what it does:

itamar@legolas ~ $ cat bash_source.sh echo "${BASH_SOURCE[*]}" itamar@legolas ~ $ ./bash_source.sh ./bash_source.sh itamar@legolas ~ $ source bash_source.sh bash_source.sh        

Cool. Looks like information technology contains the script path. Lets check it more thoroughly:

itamar@legolas ~ $ cd foobar/ itamar@legolas foobar $ cat caller_execute.sh echo "From foobar/caller_execute: "${BASH_SOURCE[*]}"" ./../bash_source.sh itamar@legolas foobar $ ./caller_execute.sh From foobar/caller_execute: ./caller_execute.sh ./../bash_source.sh itamar@legolas foobar $ source caller_execute.sh From foobar/caller_execute: caller_execute.sh ./../bash_source.sh itamar@legolas foobar $ cat caller_source.sh  echo "From foobar/caller_source: "${BASH_SOURCE[*]}"" source ../bash_source.sh itamar@legolas foobar $ ./caller_source.sh From foobar/caller_source: ./caller_source.sh ../bash_source.sh ./caller_source.sh itamar@legolas foobar $ source caller_source.sh From foobar/caller_source: caller_source.sh ../bash_source.sh caller_source.sh        

OK. So the BASH_SOURCE array consistently holds the paths to called scripts, over all combinations of execution and sourcing. Specifically, BASH_SOURCE[0] consistently stores the path of the current script. Run across likewise this commodity on BASH_SOURCE.

Given that, a new valid strategy to become the script directory may be to employ ${BASH_SOURCE[0]} instead of $0 in the first attempt.

itamar@legolas ~ foobar $ cd  itamar@legolas ~ $ cat bar.sh SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"  echo "$SCRIPT_DIR" itamar@legolas ~ $ ./bar.sh /Users/itamar  itamar@legolas ~ $ cd foobar/ itamar@legolas foobar $ ./../bar.sh /Users/itamar  itamar@legolas foobar $ source ../bar.sh /Users/itamar        

Failure of second attempt

This seems good, but it fails when symbolic links are involved:

itamar@legolas ~ $ cd foobar/  itamar@legolas foobar $ ln -s ../bash_source.sh bash_source_symlink.sh itamar@legolas foobar $ ./bash_source_symlink.sh ./bash_source_symlink.sh itamar@legolas foobar $ source bash_source_symlink.sh bash_source_symlink.sh itamar@legolas foobar $ ln -s ../bar.sh baz.sh itamar@legolas foobar $ ./baz.sh  /Users/itamar/foobar itamar@legolas foobar $ source baz.sh /Users/itamar/foobar  itamar@legolas foobar $ cd .. itamar@legolas ~ $ ./foobar/bash_source_symlink.sh ./foobar/bash_source_symlink.sh itamar@legolas ~ $ source foobar/bash_source_symlink.sh foobar/bash_source_symlink.sh itamar@legolas ~ $ ./foobar/baz.sh /Users/itamar/foobar itamar@legolas ~ $ source foobar/baz.sh /Users/itamar/foobar        

In all the failure examples in a higher place, BASH_SOURCE[0] stored the path of the symlink, not the target.

If you know that your script will never be executed or sourced via a symlink – you tin stop here. This solution is simple and good enough nether this supposition.

Final effort: resolve symlinks in $BASH_SOURCE

The tertiary and terminal solution adds symlink-resolution to the second arroyo.

itamar@legolas ~ $ cat bar.sh get_script_dir () {      SOURCE="${BASH_SOURCE[0]}"      # While $SOURCE is a symlink, resolve it      while [ -h "$SOURCE" ]; do           DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"           SOURCE="$( readlink "$SOURCE" )"           # If $SOURCE was a relative symlink (so no "/" as prefix, need to resolve it relative to the symlink base directory           [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"      washed      DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"      echo "$DIR" } repeat "$(get_script_dir)" itamar@legolas ~ $ ./bar.sh /Users/itamar itamar@legolas ~ $ source bar.sh /Users/itamar itamar@legolas ~ $ ./foobar/baz.sh /Users/itamar itamar@legolas ~ $ source foobar/baz.sh /Users/itamar        

This is based on this splendid StackOverflow answer.

This iteration is pretty robust and consistent in the face of aliases, sourcing, and other execution variants.

Failure of the 3rd attempt

Information technology is not robust if you cd to a different directory before calling the function:

itamar@legolas ~ $ cat bar.sh get_script_dir () {      SOURCE="${BASH_SOURCE[0]}"      while [ -h "$SOURCE" ]; do           DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"           SOURCE="$( readlink "$SOURCE" )"           [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"      done      $( cd -P "$( dirname "$SOURCE" )" )      pwd } cd foobar echo "$(get_script_dir)" itamar@legolas ~ $ ./bar.sh /Users/itamar/foobar        

Also watch out for $CDPATH gotchas..!

Summary

I explored a 3-step development of solutions for the same problem. Starting with a naive merely inconsistent solution, and finishing with a more complex but pretty reliable one.

Even the concluding solution presented here isn't perfect. bash scripting environs is very flexible, resulting scenarios that even the final solution is wrong. As always, choosing the solution to utilise in your script is a tradeoff between complication, performance and correctness. I found that for me, in nigh cases, the solution from the second strategy is good plenty.

Do you have another strategy that you lot use? Call back you came up with a version that always works? Permit me know through the comments!

nickersonwithou.blogspot.com

Source: https://www.ostricher.com/2014/10/the-right-way-to-get-the-directory-of-a-bash-script/

0 Response to "What Is the Correct Way to Save the Current Directory to a Variable?"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel