I'll try to answer the question in the subject here.
Globbing is the expansion of a wildcard pattern such as *.txt
into the list of file paths matching them.
files=(~/*.txt)
assigns the list of non-hidden files in ~
whose name ends in .txt
as separate elements of the $files
array (with variation in behaviour between shells when the glob doesn't match any file).
XOR(A, B)
is true if A
or B
are true but not both, so is like AND(OR(A, B), NOT(AND(A, B)))
While I don't know of any shell that has a XOR
glob/wildcard operator, several shells have a OR
and NOT
, which means AND
(where AND(X, Y)
can also be written NOT(OR(NOT(X), NOT(Y)))
can also be expressed there and as a result XOR
as well (zsh also has a AND-NOT
, which makes AND
AND-NOT(X, NOT(Y))
).
If we combine those formulas, we get:
XOR(A, B) == NOT(OR(NOT(OR(A, B)), NOT(NOT(NOT(OR(NOT(A), NOT(B)))))))
Which we can simplify (removing the double-negation) to:
XOR(A, B) == NOT(OR(NOT(OR(A, B)), NOT(OR(NOT(A), NOT(B)))))
Once translated to ksh
or bash -o extglob
(or zsh -o kshglob
) globs, that becomes:
!(!(A|B)|!(!(A)|!(B)))
Or with zsh -o extendedglob
globs:
^(^(A|B)|^(^A|^B))
Or using AND-NOT
:
XOR(A, B) == AND-NOT(OR(A, B), NOT(NOT(AND-NOT(A, NOT(B)))))
Which we can simplify to:
XOR(A, B) == AND-NOT(OR(A, B), AND-NOT(A, NOT(B)))
Which once translated to zsh -o extendedglob
globs:
(A|B)~(A~^B)
So for instance, to find files that match foo*
and *.txt
, but not both, you could:
with ksh
or bash -o extglob
(or zsh -o kshglob
):
files=(!(!(foo*|*.txt)|!(!(foo*)|!(*.txt))))
Or with zsh -o extendedglob
:
files=(^(^(foo*|*.txt)|^(^foo*|^*.txt)))
Or:
files=((foo*|*.txt)~(foo*~^*.txt))
POSIX shells have a binary (bitwise) XOR arithmetic operator: $(( 1 ^ 2 ))
expands to 3
because that's XOR(0b01, 0b10)
.
So you could define a xor
function that returns true if either but not both the evaluation of two pieces of shell code return true with something like:
xor() {
eval "$1"; s1=$((!$?))
eval "$2"; s2=$((!$?))
[ "$((s1 ^ s2))" -ne 0 ]
}
match() {
case $1 in
($2) true;;
(*) false;;
esac
}
for file in *; do
if
xor 'match "$file" "foo*"' \
'match "$file" "*.txt"' &&
then
printf '%s\n' "$file"
fi
done
if [ -d $HOME/www]; then ...; else ...; fi
does the trick. – berndbausch Feb 19 '21 at 09:26