如何对“test”的两个表达式进行异或?

如何对“test”的两个表达式进行异或?

我需要检查一个目录(我们称之为dir)是否包含两个文件之一(我们称之为fileafileb),但既不包含一个文件,也不包含两个文件。

理想的解决方案是在谓词之间使用 XOR 运算:

if [ -f dir/filea ] ^ [ -f dir/fileb]
then
    echo Structure ok
    # do stuff
fi

然而,shell 不支持^作为 XOR 运算符,并且该[命令没有选项-X--xor像它那样具有-aand -o...使用否定相等也不起作用:

if ! [ -f dir/filea -eq -f dir/fileb ]
# or
if ! [ -f dir/filea = -f dir/fileb ]

有没有什么方法可以实现这一点,而不需要求助于像这样的成熟的 AND/OR 表达式

if { [ -f dir/filea ] || [ -f dir/fileb ]; } && ! { [ -f dir/filea ] && [ -f dir/fileb ]; }

最后一个表达式变得难以阅读,并且当然我的实际路径比dir/fileX.

编辑:我的目标是 POSIX 兼容版本sh,但我对特定于其他 shell 的扩展持开放态度(主要是出于好奇,但也因为我在其他项目上使用bashksh93,这可能在那里有用)

答案1

test ...或命令的退出代码[ ... ]是测试结果。您可以使用变量来存储各个测试的结果并在以后进行比较。

[ -f dir/filea ]
testA=$?
[ -f dir/fileb ]
testB=$?
if [ "$testA" -ne "$testB" ]
then
   echo "exactly one file"
else
   echo "both files or none"
fi

这可能会导致[两个测试产生不同的非零退出代码。
根据规范中https://pubs.opengroup.org/onlinepubs/007904875/utilities/test.html,退出代码>1表示“发生错误”。您必须定义[报告错误时应该发生的情况。

为了避免这种情况,您可以使用类似于以下的条件变量赋值拘萨罗南达的回答...

testA=0
testB=0
[ -f dir/filea ] || testA=1
[ -f dir/fileb ] || testB=1
if [ "$testA" -ne "$testB" ]
then
   echo "exactly one file"
else
   echo "both files or none"
fi

...或使用否定(如注释中所述)来确保该值是01
(参见“2.9.2 管道”-“退出状态”https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_02

! [ -f dir/filea ]
testA=$?
! [ -f dir/fileb ]
testB=$?
if [ "$testA" -ne "$testB" ]
then
   echo "exactly one file"
else
   echo "both files or none"
fi

两种变体都以与“文件不存在”相同的方式处理错误。

答案2

下面对和^的值使用算术异或运算符。如果相应的文件存在并且是普通文件,则这些变量为 1;否则,它们为零。ab

a=0
b=0

[ -f dir/filea ] && a=1
[ -f dir/fileb ] && b=1

[ "$(( a ^ b ))" -eq 1 ] && echo OK

另一种方法是计算测试成功的次数:

ok=0

[ -f dir/filea ] && ok=$(( ok + 1 ))
[ -f dir/fileb ] && ok=$(( ok + 1 ))

[ "$ok" -eq 1 ] && echo OK

答案3

值得注意的是,复合命令具有退出状态,就像任何其他命令一样,因此您可以使用与大多数类 C 语言中if a; then b; else c; fi相同的方式。a ? b : c

a^b将≡翻译a ? !b : b成 shell,我们得到:

if if [ -f fileA ]
   then ! [ -f fileB ]
   else   [ -f fileB ]
   fi
then
   echo ONE file present
else
   echo NO or BOTH files present
fi

(注意if if不是一个错字。)

我承认,必须重复其中一项测试有点冗长,因此我将它们垂直对齐,以明确它们除了反转之外是相同的!

从好的方面来说,这避免了与临时变量保持$?或类似的混乱,并且它(勉强)是性能最好的。

最后,我要指出的是,该命令的-a和选项是有问题的:它们可能导致解析歧义,从而导致错误结果。只需使用两个用或分隔的测试命令即可。-otest&&||

答案4

另一种选择是使用支持xor.例如,使用 perl:

$ mkdir dir
$ touch dir/filea
$ perl -le 'print (((-f $ARGV[0]) xor (-f $ARGV[1])) ? 0 : 1)' dir/filea  dir/fileb
0

当然,您可以使用命令替换将输出捕获到变量中。

或者,如果您希望结果作为退出代码,可以直接与if&&或 一起使用||

$ perl -le 'exit (((-f $ARGV[0]) xor (-f $ARGV[1])) ? 0 : 1)'  dir/filea  dir/fileb
$ echo $?
0

注意:重要的是要认识到 Perl 对 true(非零/非空/已定义)和 false(0/空字符串/未定义)的定义与 shell 对 true(0)或 false(非-零)。这就是为什么上面的三元运算符被编写为当异或计算结果为真时返回 0,当计算结果为假时返回 1。

这对于像这样的简单脚本来说并不是很重要,但是如果您想在 Perl 脚本中进行更多布尔计算,那么您需要继续使用 Perl 对 true 和 false 的定义,直到返回 true/false 的最后一刻值壳牌期望。

顺便说一句,如果您认为这有点奇怪,那么您是对的。这是。不过,这确实有道理,而且有充分的理由。但壳牌公司却是个例外。大多数语言将 true 定义为非零,将 false 定义为零(并且许多语言具有特定的布尔数据类型,或者只允许将整数视为布尔值...其他类型需要转换。perl 在解释方面非常灵活真假)。

无论如何,Shell 会以相反的方式执行此操作,因为它大多数/经常需要知道程序是否成功退出 (0) 还是带有错误代码(从 1 到 127,具体取决于程序,每个都指示不同的错误情况)。这并不是真正的真与假,而是成功与错误代码。任何其他需要测试从外部程序返回的退出代码的程序都必须执行相同的操作,无论语言本身的 true/false 内部定义如何。

注 2:不建议在 shell 循环中重复运行 perl(或任何外部程序)。在快速、现代的系统上,启动开销可能只有几毫秒,但是这会导致数百或数千次循环迭代,并使其变得极其缓慢。如果您绝对必须在 shell 循环中执行此操作,最好找到另一种方法。否则,如果您需要在循环中进行测试并且无法重复分叉外部程序,那么最好用 perl 或 awk 或 python 或任何其他语言重写整个脚本(或其时间敏感部分) - 不是 - 壳。看为什么使用 shell 循环处理文本被认为是不好的做法?有关此主题的更多信息。

相关内容