如何从文件路径中提取目录路径?

587
在Bash中,如果VAR="/home/me/mydir/file.c",我该如何获取"/home/me/mydir"

一个更为复杂和精密的真实目录路径解析在这里:https://dev59.com/3V0a5IYBdhLWcg3w68cz#55254754 - Arunas Bartisius
9个回答

928

dirnamebasename是您需要提取路径组件的工具:

$ VAR='/home/pax/file.c'
$ DIR="$(dirname "${VAR}")" ; FILE="$(basename "${VAR}")"
$ echo "[${DIR}] [${FILE}]"
[/home/pax] [file.c]

它们不是内部bash命令,而是POSIX标准的一部分 - 请参阅dirnamebasename。因此,它们可能在大多数能够运行bash的平台上可用或可以获取。


4
为什么要在变量名周围使用括号,而不是例如 "$VAR"? - user658182
4
这篇文章回答了一个问题:在Shell变量周围何时需要大括号。 - user658182
1
在这个特定的例子中,这是出于习惯而不是必要性。 - Abandoned Cart
export 是不必要的,而 echo 是无用的。 - tripleee
2
@tripleee:export 是我的习惯,只是为了确保变量传递到子 shell。echo 语句是为了展示如何将输出存入变量,但我可能应该完全做到这一点(现在已经做到了)。虽然这两个都不会真正影响答案的“核心”,但我会进行调整。我非常感谢对改进我的回答提出建设性批评。 - paxdiablo

139
$ export VAR=/home/me/mydir/file.c
$ export DIR=${VAR%/*}
$ echo "${DIR}"
/home/me/mydir

$ echo "${VAR##*/}"
file.c

为避免与basenamedirname产生依赖关系

3
因为两者都是POSIX的一部分,所以依赖关系不应该成为问题。 - orkoden
3
orkoden,你说得对。我的回答旨在表明没有必要执行两个额外的过程。对于该用例,bash本身就已经足够了。 - Emmanuel Devaux
3
我正在使用Emmanuel的方法,因为我希望传递文件或文件夹名称,然后计算文件夹路径。使用这个正则表达式可以做到正确的事情,而dirname函数在输入文件夹时返回父文件夹。 - AnneTheAgile
12
然而,如果$VAR中没有路径信息,${VAR%/*}/test会产生一个意外的值,等于$VAR/test,而$(dirname $VAR)将产生更可预测和适当的值./test。这很重要,因为前者会尝试将文件名视为目录,而后者则不会出现问题。 - davemyron
2
这应该是被接受的答案。dirnamebasename有它们的用处,但如果路径已经在一个shell变量中,使用shell内置的工具比调用外部进程更高效、更优雅。 - tripleee
显示剩余2条评论

43

相关的是,如果你只有文件名或相对路径,dirname本身是无法帮助你的。对我来说,答案最终变成了readlink


fname='txtfile'    
echo $(dirname "$fname")                # output: .
echo $(readlink -f "$fname")            # output: /home/me/work/txtfile

你可以将这两个组合起来,以获取仅目录。

echo $(dirname $(readlink -f "$fname")) # output: /home/me/work

1
如果存在超过一个路径组件不存在,您应该使用readlink -m“$fname”递归地将给定名称规范化。 - EDkan

7
如果您希望目标文件成为符号链接,首先可以检查并获取原始文件。下面的 if 语句可能会对您有所帮助。
if [ -h $file ]
then
 base=$(dirname $(readlink $file))
else
 base=$(dirname $file)
fi

5
HERE=$(cd $(dirname $BASH_SOURCE) && pwd)

你可以通过 new_path=$(dirname ${BASH_SOURCE[0]}) 获取完整路径。使用 cd new_path 更改当前目录,然后运行 pwd 以获取当前目录的完整路径。


太棒了,最多态的选项! - Lucky Brain
1
这似乎实际上是对不同问题的回答。[引用已经损坏了。] (https://dev59.com/NWkw5IYBdhLWcg3wMHum) - tripleee

4
我正在尝试这个,并提出了一个替代方案。
$ VAR=/home/me/mydir/file.c

$ DIR=`echo $VAR |xargs dirname`

$ echo $DIR
/home/me/mydir

我喜欢的部分是可以轻松扩展备份树:
$ DIR=`echo $VAR |xargs dirname |xargs dirname |xargs dirname`

$ echo $DIR
/home

3
你可以尝试使用类似于如何使用'cut'查找最后一个字段的方法,例如:

解释

  • rev/home/user/mydir/file_name.c 反转为 c.eman_elif/ridym/resu/emoh/
  • cut 使用 / 作为分隔符,并选择第二个字段,即 ridym/resu/emoh/,它删除了第一个出现的 / 之前的字符串
  • 最后,我们再次反转它以获得 /home/user/mydir
将原始答案翻译为"最初的回答"。
$ VAR="/home/user/mydir/file_name.c"
$ echo $VAR | rev | cut -d"/" -f2- | rev
/home/user/mydir

不建议将“/”(文件名中无效)替换为空格(文件名中有效)。如果您必须使用这样的解决方案,最好只删除最后一个“/”字符及其之前的所有内容。 - Martyn Davis
@MartynDavis 请查看我的更新答案,基于您的建议。 - alper
这不是原始提问者所请求的。 - OldBuildingAndLoan
1
@m42 根据 OP 的要求更新了我的答案。请查看更新后的版本。 - alper
@alper;是的,这正是OP所要求的。 :) - OldBuildingAndLoan
@m42,谢谢你提醒我。 - alper

1

以下是我用于递归修剪的脚本。当然,将$1替换为您想要的目录。

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循环遍历find的输出是一种反模式,也是许多错误的根源。这种结构本质上是有限制的,如果find产生包含空格或其他shell元字符(更不用说换行符)的结果,则会失败。 - tripleee

0

我喜欢我的路径是绝对的,并且我需要从(第一个)bash参数获取它(不是变量,不是$pdf,不是脚本位置):

也就是说,要可靠地在我选择的文件($1)旁边创建一个子文件夹:

rp="$(realpath $(dirname $1)/pfd_extract)"
mkdir -p "$rp"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接