520

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

oguz ismail
  • 39,105
  • 12
  • 41
  • 62
Talespin_Kit
  • 19,119
  • 27
  • 87
  • 124
  • A much more sophisticated and complex real directory path resolution is here https://stackoverflow.com/questions/29789204/bash-how-to-get-real-path-of-a-symlink/55254754#55254754 – Arunas Bartisius Jul 23 '21 at 11:34

8 Answers8

822

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.

paxdiablo
  • 814,905
  • 225
  • 1,535
  • 1,899
  • 4
    Why the use of brackets around the variable names, and not "$VAR" for example? – user658182 Jul 30 '17 at 12:34
  • 3
    https://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-around-shell-variables answers the above question. – user658182 Jul 30 '17 at 12:34
  • 1
    @user658182 In this particular example, it is done out of habit, not necessity. – Abandoned Cart Nov 14 '19 at 04:45
  • The `export` is unnecessary and the [`echo`s are useless.](http://www.iki.fi/era/unix/award.html#echo) – tripleee Aug 28 '21 at 18:09
  • 1
    @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 Aug 28 '21 at 23:59
124
$ 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

Drew Noakes
  • 284,599
  • 158
  • 653
  • 723
Emmanuel Devaux
  • 3,137
  • 3
  • 24
  • 30
  • 3
    Since both are part of POSIX a dependency should not be a problem. – orkoden Nov 15 '13 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. – Emmanuel Devaux Nov 19 '13 at 08:59
  • 2
    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. – AnneTheAgile Dec 07 '13 at 19:33
  • 11
    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 Oct 01 '14 at 18:13
  • The `export` is unnecessary here. The purpose of `export` is to make the variable visible in the environment of the shell's subprocesses, but you are not calling any subprocesses which use the environment to access this information. – tripleee Aug 28 '21 at 18:03
  • 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 Aug 28 '21 at 18:05
35

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
jerblack
  • 1,057
  • 12
  • 15
  • 1
    if more than one path component not existed, you should use `readlink -m "$fname"` to canonicalize given name recursively – EDkan Sep 20 '16 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
Tahsin Turkoz
  • 3,784
  • 25
  • 18
5
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.

aerijman
  • 2,159
  • 1
  • 19
  • 26
  • Brilliant, the most polymorphic option! – Lucky Brain Nov 26 '20 at 03:02
  • This seems to be an answer to a different question actually. [The quoting is broken.](https://stackoverflow.com/questions/10067266/when-to-wrap-quotes-around-a-shell-variable) – tripleee Aug 29 '21 at 06: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
Eurospoofer
  • 534
  • 8
  • 6
2

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
alper
  • 2,299
  • 4
  • 36
  • 73
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
tripleee
  • 158,107
  • 27
  • 234
  • 292
kmchen
  • 41
  • 5
  • 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 Aug 28 '21 at 18:08