如果当前目录不存在,如何使我的Bash脚本失败?

4

如何在Bash脚本中检测当前目录不存在,并以像“您的当前目录不存在!”这样的信息干净地失败?

那么我该如何编写以下示例中有效的脚本?

$ mkdir /tmp/wow
$ cd /tmp/wow
$ pwd
/tmp/wow
$ rm -fr /tmp/wow
$ my_script.sh
Your current directory doesn't exist!

注意:如果您需要处理重命名,那么您会遇到问题。仅仅存在于目录中就会增加其引用计数,这意味着即使它从任何父级都不可达,它也不是真正被删除了。 - Charles Duffy
我假设 my_script.sh 实际上存储在 /tmp/wow 以外的某个地方? - chepner
@chepner - 对,它存在于我的~/bin/文件夹中... - Brad Parks
实际上,确实有一个更好的答案,但它是与平台相关的。我们可以仅在Linux上进行吗? - Charles Duffy
3个回答

6

不可移植的答案

这取决于标准UNIX文件系统的实现细节——没有太过可怕或者不可移植的地方——以及stat(2)系统调用的具体实现方式;因此,请注意以下内容。

每个inode,包括目录inode在内,都有一个“链接计数”(link count)——它的引用次数。对于文件,这代表硬链接的数量;对于目录,来自父目录的链接为1,.目录是另一个链接,而任何子目录中的..都会增加第三个及以上的链接数。(如果一个目录是/,当然没有父链接)。

如果一个文件系统正确使用了这种计数方法,那么stat命令返回的数据将反映出该inode的链接数。因此:

#!/bin/bash
link_count=$(stat --format=%h .)    ## --format=%h requires GNU stat
if (( link_count == 0 )); then      ## 0 works w/ ext* on Linux, not guaranteed elsewhere
  echo "This directory has been deleted! Voluntarily exiting" >&2
  exit 1
fi

然而,并非所有操作系统都正确地显示这种计数。例如,在MacOS上的HFS文件系统中,即使一个目录从其父目录中取消链接(因此只存在于内存中的引用计数中——如下所述),它也不会显示少于2个链接计数。

可移植答案

简略版

虽然你当前的目录可能是“未链接”的(也就是说,没有来自可以从文件系统根遍历到它的父目录的链接),但它不可能“不存在”。作为你的当前目录,它通过保持其内存中的引用计数大于零而强制一个目录继续存在,即使不存在任何将其作为子目录的父目录。

详细版(以及有缺陷的、破损的解决方法)

如果你愿意在目录被“移动”时出现误报:

#!/bin/sh
[ -d "$PWD" ] || { echo "Your current directory doesn't exist!" >&2; exit 1; }

话虽如此,这段代码并不能检测到新的且不同的目录已经创建在您原来的目录位置上。为了解决这个问题,我们可以添加一些额外的逻辑,例如以下方式:

# perl isn't *truly* portable, in the POSIX-specified sense, but it's everywhere.
ino() { perl -e '@s=stat($ARGV[0]); printf("%s %s\n", $s[0], $s[1]);' "$@"; }
cur_ino=$(ino .)
pwd_ino=$(ino "$PWD")
if [ "$cur_ino" != "$pwd_ino" ]; then
  echo "Directory has been replaced! Following..." >&2
  cd "$PWD" || exit
fi

然而,与任何其他文件一样,持有目录的打开句柄会防止其被删除。因此,在您的示例中执行“rm -rf /tmp/wow”并不真正删除目录,直到您的shell(以及任何其他软件)不再使用它为止,因为在那之前,其内存中的引用计数不为0。
因此,当“[ -d . ]”告诉你你的目录仍然存在,即使它已被删除……这是真的!你的目录确实仍然存在,因为你的shell当前正在使用它,这迫使它继续存在。

嘿!谢谢你的建议!我已经尝试过了,但总是失败。这是当我尝试运行你建议的代码时得到的结果:shell-init: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory Your current directory doesn't exist! - Brad Parks
是的 - 这些消息是在脚本实际启动之前的设置过程中生成的,所以你对它们无能为力。 - Charles Duffy
嗯...我明白了。如果我只将脚本编辑为echo hi,那么它可以工作,但是一旦我尝试在脚本中使用$PWDpwd,它就像上面那样死掉了。如果有一种巧妙的方法来查找PWD,那么我可能可以对其进行不同的检查... - Brad Parks
再次“像上面一样死掉”,是因为我给你的代码它死了,因为我认为这就是你想要的。单独使用echo $PWD会向stderr抛出一些警告,但它仍然会打印最后存在的工作目录,并且不会导致任何形式的死亡。 - Charles Duffy
非可移植解决方案不应该有那个错误,因为它没有引用 $PWD -- 只有可移植解决方案才会。话虽如此,通过比较 stat 数据,我们确实可以做到这一点。 - Charles Duffy
显示剩余11条评论

2
通常,测试目录(包括当前工作目录)是否存在只是为了方便以下两种情况之一:
  1. 创建缺失的目录(mkdir -p
  2. 存储文件(mktemptouch等)
也许可以利用最后一种情况来检测当前工作目录(.)是否可用。
function cwd_exists() {
    local fn
    local rc
    fn=$(TMPDIR=. mktemp 2>/dev/null)
    rc=$?
    [ -e "$fn" ] && rm -f "$fn"
    return $rc;
}

将内容插入演示脚本:

home=$(mktemp -d)
cd "$home"
cwd_exists && echo 'Exists' || echo 'Gone'
mv "$home" "$home.backup"
cwd_exists && echo 'Exists' || echo 'Gone'
mv "$home.backup" "$home"
cwd_exists && echo 'Exists' || echo 'Gone'
rm -rf "$home"
cwd_exists && echo 'Exists' || echo 'Gone'
mkdir "$home"
cwd_exists && echo 'Exists' || echo 'Gone'

我认为所需的行为是:

Exists
Exists
Exists
Gone
Gone

尽管目录存在(因为您的脚本正在使用它),但当mktemp尝试写入其中时,该目录并不存在,即使另一个进程使用相同的路径名(因为inode不同)。


啊——经过编辑,这个问题不再存在(你的mv "$home" "$home.backup"情况已经覆盖了它)。 - Charles Duffy
1
这里有一个警告,即“现有但不可写”可能与“不存在”混淆,但有很多用例不涉及到这个警告。 - Charles Duffy
顺便提一下,在 MacOS 上 mktemp -p . 不是有效的用法。然而,使用 GNU mktemp 就可以正常工作了——因此不能在未链接的目录中创建新内容的规则仍然适用。 - Charles Duffy
@CharlesDuffy 好的,我你可能是指$PWD问题,我在我的编辑中解决了这个问题。睡眠有助于缓解疲劳。另外,感谢您的MacOS提示:我已经修复了它以便可移植使用。 - bishop

1
如果您想检查当前目录是否已删除,请尝试以下操作:
# First get the inode number of current dir, e.g. 117002
ls -ial | grep '\s\.$' | awk '{print $1}'

# Then try to find the inode's path in parent dir. If none, then the original dir was deleted
find .. -inum 117002

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