为什么使用 -d 检查空字符串上的文件夹是否存在会返回 true?

为什么使用 -d 检查空字符串上的文件夹是否存在会返回 true?

我在写一些脚本,写了类似的东西

ARTIFACTS="/SOME/PATH"
[ -d $ARTIFCATS ] && rm -rf $ARTIFACTS/*

发生的事情是,出于愚蠢,我执行了第二行而没有执行第一行。结果发现 [ -d "" ] 返回 true,表达式变成了

rm -rf /*

幸运的是,这只是一台测试机,而且我不是 sudo,尽管我丢失了一些数据

我的问题是,为什么 [ -d "" ] 返回 true?文档明确指出它检查路径是否存在并且是否为文件夹

我通过使用解决了这个问题

[ -e $ARTIFACTS ]
这似乎有效

干杯

答案1

1. 这两个测试返回真的

# [ -d ] && echo true || echo false
true
# [ -d $SOME_UNSET_VAR ] && echo true || echo false
true

根据 POSIX(如@Tim 所解释)。

2. 但这也错误的不是 真的如问题所述)

# [ -d "" ] && echo true || echo false
false

因为test调用时有两个参数(尽管第二个参数是空字符串)。

3. 这就是为什么最好使用( )[[ … ]]而不是( ),因为大多数(所有?)当前 shell 都提供该功能。此构造会检查您是否提供了足够的参数(否则会引发错误并中止)test[ … ]

# [[ -d ]] && echo true || echo false
bash: unexpected argument `]]' to conditional unary operator
bash: syntax error near `]]'

或者只是表现得像人们期望的那样:

# [[ -d $SOME_UNSET_VAR ]] && echo true || echo false
false

4. 而且,正如@Gilles 指出的那样,更重要的是对替换进行双引号处理。因此-d "$SOME_UNSET_VAR"扩展为-d ""并返回错误的甚至与test(等于情况 2)。因此这也与 Bourne shell 兼容sh

# [ -d "$SOME_UNSET_VAR" ] && echo true || echo false
false

已使用 bash 3.00.16(1) 和 4.1.5(1) 进行测试

答案2

这个问题已经已回答在 StackOverflow 上。它表示,根据 POSIX 标准,test如果使用一个非空参数(并且没有其他参数)调用它,则应该始终返回成功。

这也应该是这种情况test -e(事实上在我的系统上也是如此),所以要小心。

改为使用:

[ -d "$ARTIFACTS" ]

test即使变量为空,也会使用两个参数进行调用,在这种情况下返回 false。

答案3

我通过使用 [ -e $ARTIFACTS ] 解决了这个问题,这似乎有效

你是错误的。它起作用是因为$ARTIFACTS现在设置为了某样东西。

当变量未设置时,则说

[ -d $SOMEVAR ]

或者

[ -e $SOMEVAR ]

两个都评估为,true因为它意味着说

[ -d ]

[ -e ]

分别。(说[ foobar ]总是会被评估为真。)

set -u

在这种情况下很有用。 help set会告诉你:

  -u  Treat unset variables as an error when substituting.

答案4

看到您设置了变量 ARTIFACTS,并且正在检查 ARTIFCATS。可能是输入错误?

无论如何,-d 和 -e 都会对未设置的变量产生相同的结果。

因此使用双引号它会帮助你。

ARTIFACTS="/SOME/PATH"
[ -d "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS/"*

注意:如果您的“/SOME/PATH”中有任何带空格的文件夹,您提到的脚本将会中断,并出现“预期二元运算符”错误。

例子:

ARTIFACTS="/home backup/"

1) [ -d $ARTIFACTS ] && rm -rf $ARTIFACTS/*
bash: [: /home: binary operator expected

2) [ -d "$ARTIFACTS" ] && rm -rf "$ARTIFACTS"/*

会很好。不要忘记在rm调用中也加上引号(rm -rf $ARTIFACTS会愉快地删除/home然后抱怨backup/*不存在)。

此外,包括-L检查将确保它是一个目录,而不仅仅是目录的符号链接。

所以,基本上,

[ -d "$ARTIFACTS" && ! -L "$ARTIFACTS" ] && rm -rf -- "$ARTIFACTS"/*

相关内容