103

I have written a Bash script that takes an input file as an argument and reads it.
This file contains some paths (relative to its location) to other files.

I would like the script to go to the folder containing the input file, to execute further commands.

In Linux, how do I get the folder (and just the folder) from an input file?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
BobMcGee
  • 19,824
  • 10
  • 45
  • 57

7 Answers7

179

To get the full path use:

readlink -f relative/path/to/file

To get the directory of a file:

dirname relative/path/to/file

You can also combine the two:

dirname $(readlink -f relative/path/to/file)

If readlink -f is not available on your system you can use this*:

function myreadlink() {
  (
  cd "$(dirname $1)"         # or  cd "${1%/*}"
  echo "$PWD/$(basename $1)" # or  echo "$PWD/${1##*/}"
  )
}

Note that if you only need to move to a directory of a file specified as a relative path, you don't need to know the absolute path, a relative path is perfectly legal, so just use:

cd $(dirname relative/path/to/file)

if you wish to go back (while the script is running) to the original path, use pushd instead of cd, and popd when you are done.


* While myreadlink above is good enough in the context of this question, it has some limitation relative to the readlink tool suggested above. For example it doesn't correctly follow a link to a file with different basename.

Chen Levy
  • 15,438
  • 17
  • 74
  • 92
  • 18
    Note that `readlink -f` does not work on OS X, unfortunately. – Emil Sit Jul 10 '13 at 17:33
  • I don't want the whole path, I just want folder. But this offers enough to be helpful: readlink -f relativeFileLocation | xargs dirname – BobMcGee Jul 10 '13 at 17:38
  • 2
    @EmilSit : you are right -- natively. But you can `brew install coreutils` to get it. http://stackoverflow.com/a/4031502/1022967 . – mpettis Feb 26 '16 at 18:47
  • dirname returns . if your in the same directroy. downvote. – basickarl Sep 23 '16 at 10:02
  • @Karl Morisson, indeed `dirname foo` will return `.`, but this if you `cd` into it, and ask for the `pwd`, you will get the correct answer. Please explain your objection. – Chen Levy Sep 23 '16 at 18:54
  • 3
    Please do not recommend using `${1%/*}` or `${1##*/}` as substitutes for `dirname` and `basename`. The first breaks if you give it a simple filename (`dirname foo.bar` would, correctly, be `.`), and the second breaks for a directory ending with a slash (`basename /foo/bar/`, would, correctly, be `bar`). – Camilo Martin Sep 29 '16 at 16:04
  • @CamiloMartin, you are correct, that `$(basename $1) != ${1##*/}` and `$(dirname $1) != ${1%/*}`, but `${1%/*}` and `${##*/}` complement each other well enough to work as well as `$(dirname $1)` and `$(basename $1)`, and although your comment is valid, I think it is worthwhile to make this option known too. – Chen Levy Sep 29 '16 at 16:25
  • 1
    @ChenLevy Some people may be inclined to use it for the sake of being faster (I think `basename`/`dirname` aren't built-ins), and they'll see it working at first but suddenly breaking at some point in the future for seemingly no good reason. I'd avoid suggesting it as an option, or disclaim that it's not really a bulletproof susbtitute. – Camilo Martin Sep 29 '16 at 18:09
  • If you're going to brew install something to work around readlink differences anyway, you could use `python -c"import os; print os.path.abspath('../..')"`. At least it's more readable than a riddle like `${1##*/}`.. – mvr Nov 25 '17 at 04:16
  • hope your filename contains no spaces or tabs or new lines or * or ? or [ characters. – Hello71 Jun 05 '18 at 20:21
  • [@Hello71](https://stackoverflow.com/users/335964/), I updated my answer to address your concerns. – Chen Levy May 22 '19 at 06:41
26

Take a look at realpath which is available on GNU/Linux, FreeBSD and NetBSD, but not OpenBSD 6.8. I use something like:

CONTAININGDIR=$(realpath ${FILEPATH%/*})

to do what it sounds like you're trying to do.

Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
6

This will work for both file and folder:

absPath(){
    if [[ -d "$1" ]]; then
        cd "$1"
        echo "$(pwd -P)"
    else 
        cd "$(dirname "$1")"
        echo "$(pwd -P)/$(basename "$1")"
    fi
}
Jahid
  • 21,542
  • 10
  • 90
  • 108
6
$cat abs.sh
#!/bin/bash
echo "$(cd "$(dirname "$1")"; pwd -P)"

Some explanations:

  1. This script get relative path as argument "$1"
  2. Then we get dirname part of that path (you can pass either dir or file to this script): dirname "$1"
  3. Then we cd "$(dirname "$1"); into this relative dir
  4. pwd -P and get absolute path. The -P option will avoid symlinks
  5. As final step we echo it

Then run your script:

abs.sh your_file.txt
Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
1

Try our new Bash library product realpath-lib over at GitHub that we have given to the community for free and unencumbered use. It's clean, simple and well documented so it's great to learn from. You can do:

get_realpath <absolute|relative|symlink|local file path>

This function is the core of the library:

if [[ -f "$1" ]]
then
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

It's Bash 4+, does not require any dependencies and also provides get_dirname, get_filename, get_stemname and validate_path.

AsymLabs
  • 933
  • 9
  • 15
0

Problem with the above answer comes with files input with "./" like "./my-file.txt"

Workaround (of many):

    myfile="./somefile.txt"
    FOLDER="$(dirname $(readlink -f "${ARG}"))"
    echo ${FOLDER}
Mike Q
  • 6,716
  • 5
  • 55
  • 62
-1

I have been using readlink -f works on linux

so

FULL_PATH=$(readlink -f filename)
DIR=$(dirname $FULL_PATH)

PWD=$(pwd)

cd $DIR

#<do more work>

cd $PWD
Mehul Rathod
  • 1,244
  • 8
  • 7
  • 5
    don't use uppercase variable names in Bash, it will one day clash with already existing variables... oh, and this day is now: did you know the variable `PWD` already exists in Bash an its value is actually the current directory? And use more quotes too. – gniourf_gniourf Jul 10 '13 at 21:45