One bracket, one position
◈ 10 cardsReading and writing bracket classes — sets, ranges and negation — and the three ways they are routinely misread on an assignment.
A bracket constrains exactly one character position
A bracket expression matches one character of a filename, chosen from a set you write out. [abc] matches an a, a b, or a c — one of them, never two, never zero. Everything difficult about brackets comes from forgetting that sentence.
Inside the brackets you may write:
- literal characters, in any order:
[cbA]and[Abc]are the same set. - ranges, written with a hyphen:
[0-9],[a-z],[A-Z]. A range means "every character whose code lies between these two, inclusive", using the machine's character codes — which is why[a-Z]is not a range at all. - negation, written as
[!…](the POSIX spelling) or[^…](the widely-supported alias the textbook uses).[!5]matches any single character other than5.
You can combine them: [0-9a-zA-Z] is one position that must be alphanumeric, and [!0-9a-zA-Z] is one position that must not be.
Worked example — two patterns, one directory
The directory holds six files:
lab1.c lab1a.c lab2b.c lab5c.c lab12x.c labxy.c
Pattern one: lab[0-9]?.c. Take it one candidate at a time. The pattern demands the literal letters l, a, b, then one digit, then any one character, then a literal .c — a name of exactly eight characters.
lab1.c— afterlabcomes1, good; the?must then consume., and the pattern still needs.cbut onlycis left. No match.lab1a.c—1is a digit,afills the?,.cmatches. Match.lab2b.c— same shape. Match.lab5c.c— same shape; the5is a perfectly ordinary digit here. Match.lab12x.c—1is the digit,2fills the?, and then the pattern wants.but findsx. No match.labxy.c—xis not a digit. No match.
Pattern two: lab[!5]*.c. Now the fourth character must be anything except 5, and the asterisk absorbs whatever is left before .c.
lab1.c— fourth character1, not a5; the asterisk matches zero characters. Match.lab1a.c,lab2b.c,lab12x.c,labxy.c— all match for the same reason.lab5c.c— fourth character is5. No match, and it is the only one excluded.
Notice that the negation excluded exactly one position, not a substring: lab12x.c contains no 5 at all, but that is irrelevant; the pattern only ever looked at character four.
The three misreadings that cost marks
A comma inside brackets is a literal comma. [6,7] is a set of three characters: 6, , and 7. It matches a filename position holding a comma just as happily as one holding a six. This trips people because the textbook itself writes [c,C] as though the comma meant "or" — it works there only because no filename in the example happens to contain a comma.
A range is a range of character codes, and letters are not contiguous. In ASCII the uppercase letters occupy codes 65–90 and the lowercase letters 97–122, with six punctuation characters in the gap. So [aA-Z] is not "a or A through Z" in the sense of "any letter" — it parses as the literal a followed by the range A–Z, giving twenty-seven characters: every uppercase letter, plus lowercase a, and no other lowercase letter. Writing [a-Z] instead is worse: it asks for a range that runs backwards, which the shell either rejects or silently treats as empty.
A bracket set is not a word. [comp] is one position that must hold c, o, m or p. It does not match the four letters comp; a pattern that did would be written comp, with no brackets at all.
Reading a whole assignment pattern
Put those together on rm *[aA-Z]??[6,7].[^j]*, which is exactly the shape an assignment asks about. Left to right: any run of characters, then one character that is a or an uppercase letter, then any two characters, then one character that is 6, , or 7, then a literal dot, then one character that is not j, then any run of characters. Note the last two: [^j] constrains only the first character after the dot. A file called report,x.json — dot, then j — survives; a file called report,x.txt does not.