获取当前目录或文件夹名称(不包括完整路径)

1125

如何在bash脚本或终端命令中获取当前工作目录/文件夹的名称?

pwd可以给出当前工作目录的完整路径,例如/opt/local/bin,但我只需要bin


2
带有完整路径,请参见:从内部获取Bash脚本的源目录 - kenorb
如果您想在 gh-pages 上设置基本路径,这将非常有用。 - mdmundo
24个回答

1479

不需要使用basename,尤其不需要运行pwd的子shell(这会增加一次昂贵的fork操作);shell可以内部使用参数展开来完成此操作:

result=${PWD##*/}          # to assign to a variable
result=${result:-/}        # to correct for the case where PWD=/

printf '%s\n' "${PWD##*/}" # to print to stdout
                           # ...more robust than echo for unusual names
                           #    (consider a directory named -e or -n)

printf '%q\n' "${PWD##*/}" # to print to stdout, quoted for use as shell input
                           # ...useful to make hidden characters readable.
请注意,如果您在其他情况下(不是使用PWD,而是其他变量保存目录名称),则可能需要修剪任何尾随斜杠。以下内容使用bash的extglob支持,即使有多个尾随斜杠也可以正常工作:
dirname=/path/to/somewhere//
shopt -s extglob           # enable +(...) glob syntax
result=${dirname%%+(/)}    # trim however many trailing slashes exist
result=${result##*/}       # remove everything before the last / that still remains
result=${result:-/}        # correct for dirname=/ case
printf '%s\n' "$result"

如果不使用 extglob,可以选择以下方法:

dirname="/path/to/somewhere//"
result="${dirname%"${dirname##*[!/]}"}" # extglob-free multi-trailing-/ trim
result="${result##*/}"                  # remove everything before the last /
result=${result:-/}                     # correct for dirname=/ case

47
${PWD##*/}和$PWD有什么区别? ${PWD##*/}表示删除变量$PWD中最后一个斜杠(/)及其左边的所有字符并返回剩下的字符串,即$PWD所在的当前目录名。而$PWD则是当前工作目录的完整路径。 - Mr_Chimp
36
@Mr_Chimp 前面的部分是一个参数扩展操作,它将剩余的目录修剪掉,只提供基本名称。我在答案中附有参数扩展文档的链接,如有任何疑问,请随意查阅。 - Charles Duffy
37
$PWD是内置的bash变量名;所有内置变量名都是大写字母(以区分它们与本地变量,后者应始终至少有一个小写字符)。 result=${PWD#*/}不会求值为/full/path/to/directory;相反,它仅剥离第一个元素,使其成为path/to/directory。使用两个#字符使模式匹配贪婪匹配尽可能多的字符。阅读答案中链接的“参数展开页面”以获取更多信息。 - Charles Duffy
9
请注意,pwd 命令输出的结果和 $PWD 的值并不总是相同的,这是由于通过符号链接进行 cd 操作所引起的复杂情况,以及 bash 是否设置了 -o physical 选项等因素。在处理自动挂载目录时,情况尤为棘手,如果记录物理路径而非逻辑路径,则可能产生一个路径,如果使用该路径,会导致自动挂载程序自动卸载正在使用的目录。 - Alex North-Keys
8
好的!有人应该为像我这样的老式Borne shell(Bourne Shell)用户编写一本书。也许已经有这样的书了?书中可以有一个脾气暴躁的老系统管理员说些像“在我们那个年代,只用 shcsh 而且如果你想让退格键正常工作,你必须读整个 stty 的手册,而我们还是很喜欢它!”这样的话。 - Red Cricket
显示剩余27条评论

547

使用basename程序。对于你的情况:

% basename "$PWD"
bin

28
basename程序的目的不就是这个吗?除了缺少引号之外,这个答案有什么问题吗? - Nacht
20
@Nacht: basename实际上是一个能够完成正确任务的外部程序,但是对于任何bash可以通过内置功能轻松处理的任务来说,运行_任何_外部程序都是愚蠢的,这会带来性能影响(fork()execve()wait()等),而且没有必要。 - Charles Duffy
6
顺便提一下,我对于这里的 basename 的反对意见 不适用dirname,因为 dirname 拥有 ${PWD##*/} 没有的功能——例如将不含斜杠的字符串转换为 .。因此,虽然使用 dirname 作为外部工具会带来性能开销,但它也拥有有助于弥补同样缺陷的功能。 - Charles Duffy
77
使用 basename 显然比使用繁琐的 bash 语法更易记,虽然在效率上不如后者,但从开发者的生产力角度来看,它可能更高效。因此,如果您记得它,就用它吧。否则,basename 也同样有效。是否有人曾经试图提高 bash 脚本的性能并使其更高效呢? - P.P
8
@usr,作为一个编写可以操作邮件目录(一种存储每个电子邮件的邮箱存储格式,由qmail使用)的bash脚本的人,我可以完全肯定地说,我曾经因为需要在脚本中关注效率而付出过真实的代价。这里指的是在非嵌入式硬件上每个命令替换需要20毫秒的时间——如果您的代码只包含其中之一,循环100,000个项目将会消耗大量的时间。(当然,当您不关注编写代码的质量时,很快就会到达shell不再是正确工具的点,但这一点也更快地达到了。) - Charles Duffy
显示剩余8条评论

171
$ echo "${PWD##*/}"

​​​​​


4
除非引用参数,否则在那里使用echo是错误的。如果目录名包含多个空格或一个制表符,则它将被压缩为单个空格;如果它包含通配符,则会被扩展。正确的用法应该是 echo "${PWD##*/}" - Charles Duffy
14
@jocap...而且,我不确定“echo”实际上是否是答案的合法部分。问题是如何_获取_答案,而不是如何_打印_答案;例如,如果目标是将其分配给变量,name=${PWD##*/}就是正确的,而name=$(echo "${PWD##*/}")则是错误的(在不必要的效率方面)。 - Charles Duffy

34

你可以结合使用pwd和basename。例如:

#!/bin/bash

CURRENT=`pwd`
BASENAME=`basename "$CURRENT"`

echo "$BASENAME"

exit;

21
不行。反引号会创建子shell(因此进行了一次分叉操作)——而且在这种情况下,你使用了两次![另外,虽然是个小问题,但在风格上建议变量名应该用小写字母,除非你要将它们导出到环境或者它们是特殊的保留名称]。 - Charles Duffy
12
第二个样式问题是反引号应该被$()替换。这样做可以使代码更易读,需要转义的内容也更少。 - Jeremy Wall
3
按照惯例,环境变量(PATH、EDITOR、SHELL等)和内部shell变量(BASH_VERSION、RANDOM等)应全部大写。所有其他变量名应该是小写的。由于变量名区分大小写,这种约定避免了意外覆盖环境和内部变量的情况。 - Rany Albeg Wein
4
根据约定而定。有人可能会认为所有的shell变量都是环境变量(取决于shell),因此所有的shell变量都应该使用全大写字母。另一个人可能会根据作用域进行争论 - 全局变量使用全大写,而局部变量使用小写,并且这些变量都是全局的。还有人可能会认为将变量全部转换成大写可以与散布在shell脚本中的命令形成额外对比层次,因为这些命令都是短小但含义明确的小写单词。我可能会或者不会同意这些观点,但我见过这三种用法。 :) - dannysauer

33

使用:

basename "$PWD"

或者

IFS=/ 
var=($PWD)
echo ${var[-1]} 

将内部文件名分隔符(IFS)改回空格。

IFS= 

在 IFS 后面有一个空格。


读起来更好,少些魔法! - Bret Weinraub

21

grep怎么样:

pwd | grep -o '[^/]*$'

2
不建议使用它,因为它似乎获取了一些颜色信息,而 pwd | xargs basename 没有... 可能并不那么重要,但另一个答案更简单,并且在各种环境下更加一致。 - davidhq

13
basename $(pwd)
或者
echo "$(basename $(pwd))"

3
需要更多引号才能正确--目前,pwd命令的输出在传递给basename之前进行了字符串分割和通配符扩展。 - Charles Duffy
因此,basename "$(pwd)"虽然与仅使用basename "$PWD"相比非常低效,但与根本不调用basename而使用参数扩展相比仍然低效。 - Charles Duffy
喜欢这个是因为我简直永远都记不住 echo "${PWD##*/}" 哈哈。 - undefined

13

这个主题很棒!这里还有一种口味:

pwd | awk -F / '{print $NF}'

11

我喜欢所选答案(Charles Duffy),但如果您在符号链接的目录中并且想要目标目录的名称,请小心。不幸的是,我认为无法在单个参数扩展表达式中完成它,也许我错了。这应该可以解决问题:

target_PWD=$(readlink -f .)
echo ${target_PWD##*/}

为了看到这个,我们来做一个实验:

cd foo
ln -s . bar
echo ${PWD##*/}

报告 "bar"

DIRNAME

显示路径的前导目录(不会产生 /usr/bin/dirname 的 fork-exec):

echo ${target_PWD%/*}

例如,这将把 foo/bar/baz 转换为 foo/bar。


6
不幸的是,“readlink -f” 是 GNU 的扩展,因此在 BSD(包括 OS X)上不可用。 - Xiong Chiamiov

8
echo "$PWD" | sed 's!.*/!!'

如果您使用的是Bourne shell,或者${PWD##*/}不可用。

5
FYI, ${PWD##*/} 是 POSIX sh 的语法 -- 所有现代的/bin/sh (包括 dash、ash 等)都支持它;要在最近的系统上使用实际的 Bourne 语法,你需要在一个稍微旧一点的 Solaris 系统上。除此之外,用 echo "$PWD",省略引号可能会导致错误(如果目录名中有空格、通配符等)。 - Charles Duffy
3
是的,我使用的是一台有些陈旧的Solaris系统。我已经更新了命令以使用引号。 - anomal

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