Compiler directives or macros.
To give a full idea of how complex this gets, consider that in 9 characters I can write:
$ echo {,*}.{,*}
. . .. .git .gitignore *. file.md package.json package-lock.json README.md report.ts
tsconfig.json
This is first being expanded to echo . .* *. *.*
by the {,*}
expansions, which is then being expanded based on directory globbing rules, so .
is just a literal string while .*
is globbing to . .. .git .gitignore
while *.
is not matching anything so it is again a literal string (though I think this depends on whether a bash globbing setting is flipped or not) and *.*
is matching the other files in the directory that have at least one period in the middle of their filename, file.md package.json package-lock.json README.md report.ts tsconfig.json
.
Or even worse,
$ {ec,}{ho,}
ec ho
So we are performing what in Haskell would look like [a ++ b | a <- ["ec", ""], b <- ["ho", ""]]
before any sort of evaluation is taking place, this yields ["echo", "ec", "ho", ""]
, and only after that is this used to produce the string ec ho\n
, six characters all told including the newline. In Haskell we would call this composition "in the List Monad", it is I believe distinct from what Lisp macros can do, that being "unquote splicing".
The obvious mental model to appeal to is a multi-pass compiler. You have some lexical analysis which is tokenizing the string into "words", then a bunch of passes are going through through those "words" to get to the final command that will be executed. So for example in echo ~{crdrost,$HOME}
we see first a tokenization to echo
and ~{crdrost,$HOME}
, there are then three rules being applied in sequence:
- A brace expansion of the latter to two words,
~crdrost
and ~$HOME
,
- A tilde expansion which checks whether
crdrost
is a valid user (I am!) and replaces with /home/crdrost
, versus whether $HOME
is a valid user (it is not!) and therefore doesn't replace.
- Then there is the variable substitution in
~$HOME
to ~/home/crdrost
.
After these "compiler passes" are done we have something which can be evaluated; we look up the echo
builtin function and apply it to the argument strings to produce the string /home/crdrost ~/home/crdrost\n
, the words are emitted separated by single spaces followed by a newline character.