ChatGPT解决这个技术问题 Extra ChatGPT

How to extract directory path from file path?

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

A much more sophisticated and complex real directory path resolution is here stackoverflow.com/questions/29789204/…

p
paxdiablo

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.


Why the use of brackets around the variable names, and not "$VAR" for example?
stackoverflow.com/questions/8748831/… answers the above question.
@user658182 In this particular example, it is done out of habit, not necessity.
The export is unnecessary and the echos are useless.
@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.
D
Drew Noakes
$ 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


Since both are part of POSIX a dependency should not be a problem.
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.
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.
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.
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.
j
jerblack

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

if more than one path component not existed, you should use readlink -m "$fname" to canonicalize given name recursively
T
Tahsin Turkoz

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

a
aerijman
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.


Brilliant, the most polymorphic option!
This seems to be an answer to a different question actually. The quoting is broken.
E
Eurospoofer

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

a
alper

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

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.
@MartynDavis Please see my updated answer based on your suggestion
This is not what the OP requested.
@m42 updated my answer according to OP requested. Please see its updated version.
@alper; yeah now that's what the OP requested. :)
t
tripleee

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.