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 ...; fidoes the trick. – berndbausch Feb 19 '21 at 09:26