bashpathscripting

How can I set the current working directory to the directory of the script in Bash?


I'm writing a Bash script. I need the current working directory to always be the directory that the script is located in.

The default behavior is that the current working directory in the script is that of the shell from which I run it, but I do not want this behavior.


Solution

  • TL;DR

    #!/usr/bin/env bash
    cd "$(dirname "$0")"
    

    The explanation

    How does this work and how does it deal with edge and corner cases?


    script invocation command bash argument dirname argument cd argument
    foo (found in $PATH at /path/to/foo) /path/to/foo /path/to/foo /path/to
    bash foo foo foo .
    /foo /foo /foo /
    ./foo ./foo ./foo .
    "/pa th/to/foo" /pa th/to/foo /pa th/to/foo /pa th/to
    "./pa th/to/foo" ./pa th/to/foo ./pa th/to/foo ./pa th/to
    "../pa th/to/foo" ../pa th/to/foo ../pa th/to/foo ../pa th/to
    "../../pa th/to/foo" ../../pa th/to/foo ../../pa th/to/foo ../../pa th/to
    "pa th/to/foo" pa th/to/foo pa th/to/foo pa th/to
    --help/foo --help/foo * N/A N/A
    --help N/A ** N/A N/A

    On symlinks

    The cd command will follow symlinks if they are involved. A symlink usually exists to be followed, so in most situations following the symlink is the correct thing to do. Why would it be a problem for the code in the script to follow a symlink when it was just fine to follow that same symlink a few microseconds ago when loading the script?

    On command arguments starting with hyphens

    Elaborating on the two cases of arguments starting with hyphens in above table (marked with * and **, respectively):

    * There is only one case where the argument to the dirname could begin with a -, and that is the relative path case --help/foo. If the script is in a subdirectory named --help, the script execution will run bash --help/foo, and bash does not know that option --help/foo and will therefore abort with an error message. The script will never execute, so it does not matter what the cd "$(dirname "$0")" would have executed.

    ** Note that naming the script --help makes the shell not find the command when you are typing --help in the same directory. Alternatively, with $PATH containing the current directory, the script will be run as /path/to/--help or ./--help, always with something in front of the - character.

    Unless bash introduces command line arguments with a parameter separated by a =, it is unlikely to impossible to pass a - argument to bash which contains a / later, and which is accepted by bash.

    If you can rely on dirname accepting -- argument (bash builtin cd will certainly accept --), you can change the script snippet to

    cd -- "$(dirname -- "$0")"
    

    Please do comment if you can figure out a way to construct an argument beginning with - which can be sneaked past bash.

    Nota bene

    The TL;DR snippet also works with non-bash /bin/sh.