In Bash, if VAR="/home/me/mydir/file.c"
, how do I get "/home/me/mydir"
?
10 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
.
-
4Why the use of brackets around the variable names, and not "$VAR" for example? Commented Jul 30, 2017 at 12:34
-
5stackoverflow.com/questions/8748831/… answers the above question. 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
-
-
2@tripleee: the
export
is a habit of mine, simply to ensure the variable is passed to sub-shells. Theecho
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. Commented Aug 28, 2021 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
-
4Since both are part of POSIX a dependency should not be a problem.– orkodenCommented Nov 15, 2013 at 13:50
-
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. Commented Nov 19, 2013 at 8: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. Commented Dec 7, 2013 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. Commented Oct 1, 2014 at 18:13
-
2This should arguably be the accepted answer.
dirname
andbasename
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.– tripleeeCommented Aug 28, 2021 at 18:05
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
-
1if more than one path component not existed, you should use
readlink -m "$fname"
to canonicalize given name recursively– EDkanCommented Sep 20, 2016 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
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.
-
1
-
2This seems to be an answer to a different question actually. The quoting is broken.– tripleeeCommented Aug 29, 2021 at 6: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
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
-
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– alperCommented Dec 16, 2020 at 21:22
-
-
1@m42 updated my answer according to OP requested. Please see its updated version.– alperCommented Jul 25, 2021 at 16:05
-
@alper; yeah now that's what the OP requested. :) Commented Jul 28, 2021 at 17: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 offind
is an antipattern and a source of many bugs. This construct is inherently limited, and will fail iffind
produces results which contain whitespace or other shell metacharacters, let alone then newlines.– tripleeeCommented Aug 28, 2021 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"
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]