如何获取文件的父目录?
我希望它对所有类型的名称都是安全的:
.
..
path/to/my/file
/absolute/path/to/my/file
'-rf --no-preserve-root whatever'/test.zip
(symbolic links)
`'"`'{(})
我更感兴趣的是获取文件系统上的规范位置,而不是遍历文件名中指定的路径。
请注意,这个问题有类似的问题,但没有一个关注正确性,相对/绝对路径和"不安全"名称:
[1] bash获取当前目录的父目录
[2]检索脚本的父目录
[3] bash文件路径到文件的父目录
您是在寻找包含文件的目录的完整路径名,还是包含该文件的目录的父路径?您是否对遍历文件名中指定的路径或获取文件系统上的规范位置更感兴趣?
两个问题的第二个案例。
假设您当前的目录是/home/user/bin,并且'文件名'是..(父当前目录)。据推测,包含该条目的目录是/home/user/bin,因此父目录是/home/user,或者是否需要一些其他解释(目录..是/home/user,因此父目录可能是/home)。我认为"假定的"变体是正确的,与.类似,但也许它也可以检查。
对-rf…/test.zip名称的批准解释是什么?这是一个带有空格,短划线等的目录名吗?另外,如果文件名不存在应该怎么做?这是一个错误,还是代码应该对文件名进行文本分析,以尝试推断文件存在时的名称?
@JonathanLeffler是的,我认为你对.和我的解释是相似的。对于路径/home/user/bin,我希望"父目录"为/home/user。由于我和每个人的使用可能都非常具体,因此应该手动验证。
@JonathanLeffler是的,-rf --no-preserve-root whatever应该被视为单个路径部分。我应该把它放在引号中,好吗?我编辑了这个问题。
@JonathanLeffler至于"如果文件不存在" - 我老实说不知道。所以问题往往会超过原作者的意图,至少对于有价值的问题而言。所以我和你的解释可能会有所不同。我个人知道存在一个文件,这些案例对我来说难以区分。
真正安全的解决方案:
parent_dir="$(dirname --"$(realpath --"$file_name")")"
如果您的系统没有realpath但有readlink,则应该可以:
parent_dir="$(dirname --"$(readlink -f --"$file_name")")"
这是一个bash问题。我在OS X,FreeBSD和其他不是Linux的操作系统上使用bash,这些操作系统没有你建议的realpath命令,这不是bash的一部分。哎呀,即使在我管理的Linux系统上,realpath也可用,但默认情况下没有安装。如果您觉得有必要提出仅适用于某个特定平台的内容,至少应提及该平台。
@ghoti FreeBSD有一个realpath(1)实用程序。"Realpath实用程序首次出现在FreeBSD 4.3中。"
@kdhp:尽管macOS Sierra(以及之前的Mac OS X)是从BSD派生的,但默认情况下它们不包含realpath命令。在我的机器上,我有命令的实现和命令的GNU实现。并且,鉴于realpath未标准化(例如,通过POSIX),FreeBSD和GNU实现不太可能完全兼容。幸运的是,他们有一个共同的功能子集。在macOS上有一个readlink命令,但它的选项远远少于GNU的版本(它有-n选项,两者看起来相同)。
与仅使用dirname …相比,printf '%s
'"$(dirname …)"有什么好处?
@JonathanLeffler好问题!在我的实际脚本中,我需要一个变量,所以代码是parent_dir="$(...)"。我想我应该使用变量或避免printf层。会做第一个。再次感谢!
@JonathanLeffler FreeBSD realpath(1)实用程序可以追溯到2002年(在最初的OS X发布之后),并且在FreeBSD及其分支之外找不到。除了选项之外,FreeBSD和GNU realpath(1)应该是兼容的,因为realpath(3)功能是标准化的。但是readlink(1)将在任何GNU或BSD用户区中预设(除少数遗留边缘情况外)。
@kdhp,我的天哪,对你是对的。我早些时候检查时一定输入错误。感谢您指出了这一点。然而,它不是bash的一部分。
@kdhp:这不是一个真正的争论,而是一个FYI ......有趣的历史!此外,macOS readlink只有一个选项(-n表示没有换行符); GNU readlink有8个选项(每个选项都有一个长名称和一个简短的单字符名称)加上--help和--version。其中,-n(--no-newline)选项在两者之间是相同的。我不确定这7个选项是否是您提到的"遗留边缘案例"。我同意realpath命令的核心功能是基于realpath的功能。也许我会在电影之后将其删除......这并不是非常重要。
@JonathanLeffler你是对的,-f选项相当新(2010),macOS(OS X)倾向于使用老化的BSD实用程序。至于边缘情况,我指的是realpath(1)存在而readlink(1)不存在的情况。
Bash的cd命令有几个有趣但很少使用的选项,-P和-L。
cd [-L|[-P [-e]] [-@]] [dir]
... The -P option causes cd to use the physical directory
structure by resolving symbolic links while traversing dir and
before processing instances of .. in dir (see also the -P option
to the set builtin command); the -L option forces symbolic links
to be followed by resolving the link after processing instances
of .. in dir. ...
所以...如果你正在寻找当前工作目录的文件系统中的物理位置,你可以使用这样的东西:
realwd="$(cd -P .; pwd)"
在您的评论中,您提到您正在寻找包含文件的目录的父目录 - 因此,如果路径是/foo/bar/baz/filename,您将寻找/foo/bar。
为了得到这个,我建议结合使用cd -P和参数扩展。 由于您知道/字符永远不会作为文件名的一部分存在,因此以下内容可能对您有用:
grandparent() {
local realdir="$(cd -P"${1%/*}"; pwd)"
echo"${realdir%/*}"
}
这可以通过使用cd -P来"获取"文件的物理位置,然后使用参数扩展来去除路径中的最后一项。
$ mkdir -p one/two/three
$ touch one/two/three/foo
$ ln -s one/two/three bar
$ ls -l bar
lrwxr-xr-x 1 ghoti wheel 13 Nov 19 23:05 bar -> one/two/three
$ grandparent bar/foo
/usr/home/ghoti/tmp6/one/two
-L和-P不是bash特定的。
@kdhp - 是的,但它们也不是普遍的 - 那里仍然有tcsh用户。我提到他们是bash选项,因为OP标记了他的问题bash。我的答案中的函数应该适用于任何POSIX shell ..以及bash。 :)