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

joH*_*oH1 12 shell filenames test

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

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

if [ -f dir/filea ] ^ [ -f dir/fileb]
then
    echo Structure ok
    # do stuff
fi
Run Code Online (Sandbox Code Playgroud)

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

if ! [ -f dir/filea -eq -f dir/fileb ]
# or
if ! [ -f dir/filea = -f dir/fileb ]
Run Code Online (Sandbox Code Playgroud)

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

if { [ -f dir/filea ] || [ -f dir/fileb ]; } && ! { [ -f dir/filea ] && [ -f dir/fileb ]; }
Run Code Online (Sandbox Code Playgroud)

最后一个表达式变得不可读,当然我的实际路径比dir/fileX.

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

Bod*_*odo 17

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
Run Code Online (Sandbox Code Playgroud)

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

为了避免这种情况,您可以使用类似于Kusalananda 的答案的条件变量赋值......

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
Run Code Online (Sandbox Code Playgroud)

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

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

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


Kus*_*nda 5

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

a=0
b=0

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

[ "$(( a ^ b ))" -eq 1 ] && echo OK
Run Code Online (Sandbox Code Playgroud)

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

ok=0

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

[ "$ok" -eq 1 ] && echo OK
Run Code Online (Sandbox Code Playgroud)

  • @MartinKealey,这不是真的 `$(( a ^ b ))` 是 POSIX 并且通常是首选的(如果 `$b` 为负,而 `$(例如 (ab))` 就可以了)。确实,某些(现在)旧版本的基于 ash 的 shell 不支持它,并且规范中并不总是很清楚。 (2认同)

Mar*_*ley 5

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

\n

a^b将\xe2\x89\xa1翻译a ? !b : b成 shell,我们得到:

\n
if if [ -f fileA ]\n   then ! [ -f fileB ]\n   else   [ -f fileB ]\n   fi\nthen\n   echo ONE file present\nelse\n   echo NO or BOTH files present\nfi\n
Run Code Online (Sandbox Code Playgroud)\n

(请注意,这不是if if拼写错误。)

\n

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

\n

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

\n

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

\n