603

In Bash, if VAR="/home/me/mydir/file.c", how do I get "/home/me/mydir"?

1

10 Answers 10

953

dirname and basename are the tools you're looking for for extracting path components:

$ VAR='/home/pax/file.c'
$ DIR="$(dirname "${VAR}")" ; FILE="$(basename "${VAR}")"
$ echo "[${DIR}] [${FILE}]"
[/home/pax] [file.c]

They're not internal bash commands but they are part of the POSIX standard - see dirname and basename. Hence, they're probably available on, or can be obtained for, most platforms that are capable of running bash.

5
  • 4
    Why the use of brackets around the variable names, and not "$VAR" for example?
    – user658182
    Commented Jul 30, 2017 at 12:34
  • 4
    stackoverflow.com/questions/8748831/… answers the above question.
    – user658182
    Commented Jul 30, 2017 at 12:34
  • 1
    @user658182 In this particular example, it is done out of habit, not necessity. Commented Nov 14, 2019 at 4:45
  • The export is unnecessary and the echos are useless.
    – tripleee
    Commented Aug 28, 2021 at 18:09
  • 2
    @tripleee: the export is a habit of mine, simply to ensure the variable is passed to sub-shells. The echo statements are to show how you could get the output into a variable, but I should probably have gone the whole hog on that (which I now have). Though neither of those really affect the "meat" of the answer, I'll adjust. I'm always appreciative of constructive criticism on improving my answers.
    – paxdiablo
    Commented Aug 28, 2021 at 23:59
143
$ export VAR=/home/me/mydir/file.c
$ export DIR=${VAR%/*}
$ echo "${DIR}"
/home/me/mydir

$ echo "${VAR##*/}"
file.c

To avoid dependency with basename and dirname

7
  • 4
    Since both are part of POSIX a dependency should not be a problem.
    – orkoden
    Commented Nov 15, 2013 at 13:50
  • 3
    orkoden , you're right. The aim of my answer is to show there is no obligation to execute two additional process. bash is self sufficient for the use case. Commented Nov 19, 2013 at 8:59
  • 3
    I am using Emmanuel's method because I wish to pass either a file or a folder name, and then compute the folder path. Using this regex does the right thing, whereas the dirname function returned the parent folder when I input a folder. Commented Dec 7, 2013 at 19:33
  • 12
    However, if there's no path info in $VAR, ${VAR%/*}/test produces an unexpected value equal to $VAR/test whereas $(dirname $VAR) will produce the more predictable and appropriate value of ./test. This is a big deal because the former will attempt to treat the filename as a directory while the latter will be OK.
    – davemyron
    Commented Oct 1, 2014 at 18:13
  • 2
    This should arguably be the accepted answer. dirname and basename have their place, but if the path is already in a shell variable, using the shell's built-in facilities is more efficient and elegant than calling an external process.
    – tripleee
    Commented Aug 28, 2021 at 18:05
43

On a related note, if you only have the filename or relative path, dirname on its own won't help. For me, the answer ended up being readlink.

fname='txtfile'    
echo $(dirname "$fname")                # output: .
echo $(readlink -f "$fname")            # output: /home/me/work/txtfile

You can then combine the two to get just the directory.

echo $(dirname $(readlink -f "$fname")) # output: /home/me/work
1
  • 1
    if more than one path component not existed, you should use readlink -m "$fname" to canonicalize given name recursively
    – EDkan
    Commented Sep 20, 2016 at 12:13
7

If you care target files to be symbolic link, firstly you can check it and get the original file. The if clause below may help you.

if [ -h $file ]
then
 base=$(dirname $(readlink $file))
else
 base=$(dirname $file)
fi
6
HERE=$(cd $(dirname $BASH_SOURCE) && pwd)

where you get the full path with new_path=$(dirname ${BASH_SOURCE[0]}). You change current directory with cd new_path and then run pwd to get the full path to the current directory.

2
  • 1
    Brilliant, the most polymorphic option! Commented Nov 26, 2020 at 3:02
  • 1
    This seems to be an answer to a different question actually. The quoting is broken.
    – tripleee
    Commented Aug 29, 2021 at 6:28
4

I was playing with this and came up with an alternative.

$ VAR=/home/me/mydir/file.c

$ DIR=`echo $VAR |xargs dirname`

$ echo $DIR
/home/me/mydir

The part I liked is it was easy to extend backup the tree:

$ DIR=`echo $VAR |xargs dirname |xargs dirname |xargs dirname`

$ echo $DIR
/home
3

You could try something like this using approach for How to find the last field using 'cut':

Explanation

  • rev reverses /home/user/mydir/file_name.c to be c.eman_elif/ridym/resu/emoh/
  • cut uses / as the delimiter, and chooses the second field, which is ridym/resu/emoh/, which deletes string up to the first occurrence of /
  • lastly, we reverse it again to get /home/user/mydir
$ VAR="/home/user/mydir/file_name.c"
$ echo $VAR | rev | cut -d"/" -f2- | rev
/home/user/mydir
6
  • Replacing '/' (not valid within a file name) with space (valid within a file name) is not recommended. If you must use such a solution, better to just remove everything up until, and including, the last '/' character. Commented Dec 16, 2020 at 0:10
  • @MartynDavis Please see my updated answer based on your suggestion
    – alper
    Commented Dec 16, 2020 at 21:22
  • This is not what the OP requested. Commented Jul 25, 2021 at 15:40
  • 1
    @m42 updated my answer according to OP requested. Please see its updated version.
    – alper
    Commented Jul 25, 2021 at 16:05
  • @alper; yeah now that's what the OP requested. :) Commented Jul 28, 2021 at 17:05
1

Here is a script I used for recursive trimming. Replace $1 with the directory you want, of course.

BASEDIR=$1
IFS=$'\n'
cd "$BASEDIR"
 for f in $(find . -type f -name ' *')
 do 
    DIR=$(dirname "$f")
    DIR=${DIR:1}
    cd "$BASEDIR$DIR"
    rename 's/^ *//' *
 done
1
  • Using a for loop over the output of find is an antipattern and a source of many bugs. This construct is inherently limited, and will fail if find produces results which contain whitespace or other shell metacharacters, let alone then newlines.
    – tripleee
    Commented Aug 28, 2021 at 18:08
0

I like my paths absolute, and I need it from the (first) bash parameter (not a variable, not $pdf, not script location):

i.e. to reliably create a subfolder next to my chosen file ($1):

rp="$(realpath $(dirname $1)/pfd_extract)"
mkdir -p "$rp"
0

Using sed

VAR='/home/pax/file.c'
DIR="$(echo $VAR | sed 's|\(.*\)\/.*|\1|g')"
FILE="$(echo $VAR | sed 's|.*\/\(.*\)|\1|g')"
echo "[${DIR}] [${FILE}]"
[/home/pax] [file.c]

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