I have a bash script named except.sh which is passed a list of files/directories like so:
$ ls
a b c d/
$ ./except.sh b c
When calling except this way, it should expand to a d/ i.e. all files/directories except the given names.
Here’s how I tried to implement this:
#!/usr/bin/env bash
# enable extended globbing
shopt -s extglob
# set IFS to | so that $* expands correctly
IFS='|'
printf '%s' !("$*")
Given b c as parameters, the last line should expand to
printf '%s' !(b|c)
resulting in a d being printed. But to my suprise,
abcd
is printed. What am I doing wrong?
The problem is that
$*is in double quotes, which means that its contents will not be treated as a pattern, just likeecho "*"does not expand the asterisk. Combining the outer pattern with the inner quoted portion automatically escapes the latter, so!("b|c")is treated like!(b\|c). Negation of the nonexistentb|cfile naturally expands to all files in the directory.An additional problem is that extended globbing is messed up by
IFSbeing set to|, so you must reset it before expanding the pattern. Therefore, you must do the expansion in two steps: first, calculate the pattern, then resetIFSand expand it: