In Bash, if VAR="/home/me/mydir/file.c"
, how do I get "/home/me/mydir"
?
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
.
$ 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
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.
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
readlink -m "$fname"
to canonicalize given name recursively
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.
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 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
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
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.
Success story sharing
export
is unnecessary and theecho
s are useless.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.