From Bash Reference Manual

Expansion is performed on the command line after it has been split into tokens. There are seven kinds of expansion performed:

  • brace expansion

  • tilde expansion

  • parameter and variable expansion

  • command substitution

  • arithmetic expansion

  • word splitting

  • filename expansion

From the programming languages perspective, are shell expansions of the same kind of pre-defined compiler macros in C?

What is the concept for shell expansions called in programming languages?


2 Answers 2


Shell languages and scripting languages have specific features adapted to the context of their use. Such features may not exists in that form in more classical programming languages as they would not be inappropriate for their context of use.

A shell language has to assist the user and make him productive in an interactive context, often for commands that are executed only once and then forgotten. Syntactic preprocessing of commands for common needs, use of abbreviation, and such, is part of the interactice help provided.

Programming languages must be often more explicit, more verbose and less context dependent, as they live long and have to be maintained, which requires them to be very readable by a variety of people. Hence, in the context of a programming language, these fonctionalities may be used, but have to be explicitly called as subprograms applied to the concerned data, rather than be implicit in the shell preprocessing of a command to be executed on the spot.


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 

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:

  1. A brace expansion of the latter to two words, ~crdrost and ~$HOME,
  2. 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.
  3. 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.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.