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

- 1
- 16
- 47
- 69

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

- 854,327
- 234
- 1,573
- 1,953
-
4Why the use of brackets around the variable names, and not "$VAR" for example? – user658182 Jul 30 '17 at 12:34
-
4https://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
$ 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

- 300,895
- 165
- 679
- 742

- 3,327
- 4
- 25
- 30
-
3
-
3orkoden , 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
-
3I 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
-
12However, 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
-
1The `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
-
2This 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
-
1Another 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
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,203
- 15
- 15
-
1if 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
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

- 4,356
- 1
- 27
- 18
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,522
- 1
- 22
- 32
-
-
1This 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
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

- 614
- 10
- 7
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 bec.eman_elif/ridym/resu/emoh/
cut
uses/
as the delimiter, and chooses the second field, which isridym/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

- 2,919
- 9
- 53
- 102
-
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. – Martyn Davis Dec 16 '20 at 00:10
-
-
-
1@m42 updated my answer according to OP requested. Please see its updated version. – alper Jul 25 '21 at 16:05
-
-
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
-
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
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"

- 9,625
- 4
- 80
- 110