578

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

oguz ismail
  • 1
  • 16
  • 47
  • 69
Talespin_Kit
  • 20,830
  • 29
  • 89
  • 135
  • 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

9 Answers9

917

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
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 4
    Why the use of brackets around the variable names, and not "$VAR" for example? – user658182 Jul 30 '17 at 12:34
  • 4
    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
  • 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 Aug 28 '21 at 23:59
138
$ 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
  • 300,895
  • 165
  • 679
  • 742
Emmanuel Devaux
  • 3,327
  • 4
  • 25
  • 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
  • 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. – AnneTheAgile Dec 07 '13 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 Oct 01 '14 at 18:13
  • 1
    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
  • 1
    Another benefit of this is it is much MUCH faster than `dirname` I'm running it in a loop with 1000 items and dirname takes seconds whereas this is nearly instantaneous – user1169420 Nov 02 '22 at 23:24
42

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,203
  • 15
  • 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
  • 4,356
  • 1
  • 27
  • 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,522
  • 1
  • 22
  • 32
  • Brilliant, the most polymorphic option! – Lucky Brain Nov 26 '20 at 03:02
  • 1
    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
  • 614
  • 10
  • 7
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,919
  • 9
  • 53
  • 102
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
  • 175,061
  • 34
  • 275
  • 318
kmchen
  • 39
  • 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
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"
Frank N
  • 9,625
  • 4
  • 80
  • 110