107

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?

2
  • Are you giving the full path to the input file or just the path relative to the current working directory?
    – dmh
    Commented Jul 10, 2013 at 17:29
  • dhm: input gives relative path, I want absolute path minus filename.
    – BobMcGee
    Commented Jul 10, 2013 at 17:34

7 Answers 7

184

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.

12
  • 19
    Note that readlink -f does not work on OS X, unfortunately.
    – Emil Sit
    Commented Jul 10, 2013 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
    Commented Jul 10, 2013 at 17:38
  • 2
    @EmilSit : you are right -- natively. But you can brew install coreutils to get it. stackoverflow.com/a/4031502/1022967 .
    – mpettis
    Commented Feb 26, 2016 at 18:47
  • 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). Commented Sep 29, 2016 at 16:04
  • 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. Commented Sep 29, 2016 at 18:09
28

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.

2
  • realpath does an odd to me where it resolves symlinks of relative dirs Commented Jun 8, 2015 at 20:19
  • Thanks! Worked for me on Mac OS Commented Oct 7, 2016 at 13:27
7

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
}
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
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.

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}
-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
2
  • 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. Commented Jul 10, 2013 at 21:45
  • pushd / popd is much better approach and does not use any bash vars Commented Jun 7 at 12:55

Not the answer you're looking for? Browse other questions tagged or ask your own question.