How-to: Command, filename and directory stack substitution

Substitution allows the output of a command to replace the command itself.

Command substitution

Command substitution occurs when a command is enclosed as follows:


The output from such a command is broken into separate words at blanks, tabs and newlines, and null words are discarded.
The output is variable and command substituted and put in place of the original string with any trailing newlines deleted.
The $() form is the modern way and has more clarity and readability particularly when nesting multiple commands.

Command substitutions inside double quotes (") retain blanks and tabs; only newlines force new words. The single final newline does not force a new word in any case. It is thus possible for a command substitution to yield only part of a word, even if the command outputs a complete line.

Embedded newlines are not deleted, but they can be removed during word splitting.
The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

When the older `backtick` form of substitution is used, it will substitute Standard Output only, not Standard Error.
Also when parsing the command, a backslash will retain its literal meaning except when followed by $, ` or \.
The first backquote not preceded by a backslash terminates the command substitution.

When using the $(command) form, all characters between the parentheses make up the command; none are treated specially.

Command substitutions can be nested. To nest when using the backquoted form, escape the inner backquotes with backslashes. If you are nesting substitutions the $(command) form is much easier to read: $(echo $(echo inside))

If the substitution appears within double quotes, word splitting and filename expansion are not performed on the results.

Process substitution operators:



command can be any command that produces output on stdout. Bash execs the command, creates a named pipe from the output, and replaces the operator with the name of that pipe. You can then read stdout from that pipe as you would from a regular file. When execution is finished, the named pipe is removed automatically.

Filename substitution

If a word contains any of the characters *, ?, [ or { or begins with the character ~ it is a candidate for filename substitution, also known as `globbing'. This word is then regarded as a pattern (`glob-pattern'), and replaced with an alphabetically sorted list of file names which match the pattern.

In matching filenames, the character . at the beginning of a filename or immediately following a / , as well as the character / must be matched explicitly.
The character * matches any string of characters, including the null string.
The character ? matches any single character.
The sequence [...] matches any one of the characters enclosed. Within [...], a pair of characters separated by - matches any character lexically between the two.

(+) Some glob-patterns can be negated: The sequence [^...] matches any single character not specified by the characters and/or ranges of characters in the braces.

An entire glob-pattern can also be negated with ^:

    > echo *
    bang crash crunch ouch
    > echo ^cr*
    bang ouch

    Glob-patterns which do not use ?, *, or [] or which use  {}  or
       ~ (below) are not negated correctly.

The metanotation `a{b,c,d}e' is a shorthand for `abe ace ade'. Left- to-right order is preserved: `/usr/source/s1/{oldls,ls}.c' expands to `/usr/source/s1/oldls.c /usr/source/s1/ls.c'. The results of matches are sorted separately at a low level to preserve this order:
`../{memo,*box}' might expand to `../memo ../box ../mbox'. (Note that `memo' was not sorted with the results of matching `*box'.) It is not an error when this construct expands to files which do not exist, but it is possible to get an error from a command to which the expanded list is passed. This construct may be nested. As a special case the words `{', `}' and `{}' are passed undisturbed.

The character ~ at the beginning of a filename refers to home directories. Standing alone, i.e., ~ , it expands to the invoker’s home directory as reflected in the value of the home shell variable. When followed by a name consisting of letters, digits and - characters the shell searches for a user with that name and substitutes their home directory; thus `~ken' might expand to `/usr/ken' and `~ken/chmach' to `/usr/ken/chmach'. If the character ~ is followed by a character other than a letter or / or appears elsewhere than at the beginning of a word, it is left undisturbed. A command like `setenv MANPATH /usr/man:/usr/local/man:~/lib/man' does not, therefore, do home directory substitution as one might hope.

It is an error for a glob-pattern containing *, ?, [ or ~, with or without ^, not to match any files. However, only one pattern in a list of glob-patterns must match a file (so that, e.g., `rm *.a *.c *.o' would fail only if there were no files in the current directory ending in .a, .c, or .o), and if the nonomatch shell variable is set a pattern (or list of patterns) which matches nothing is left unchanged rather than causing an error.

The noglob shell variable can be set to prevent filename substitution, and the expand-glob editor command, normally bound to `^X-*', can be used to interactively expand individual filename substitutions.

Directory stack substitution (+)

The directory stack is a list of directories, numbered from zero, used by the pushd, popd and dirs builtin commands (q.v.). dirs can print, store in a file, restore and clear the directory stack at any time, and the savedirs and dirsfile shell variables can be set to store the directory stack automatically on logout and restore it on login. The dirstack shell variable can be examined to see the directory stack and set to put arbitrary directories into the directory stack.

The character = followed by one or more digits expands to an entry in the directory stack. The special case =- expands to the last directory in the stack. For example,

    > dirs -v
    0    /usr/bin
    1    /usr/spool/uucp
    2    /usr/accts/sys
    > echo =1
    > echo =0/calendar
    > echo =-

The noglob and nonomatch shell variables and the expand-glob editor command apply to directory stack as well as filename substitutions.

Other substitutions (+)

There are several more transformations involving filenames, not strictly related to the above but mentioned here for completeness. Any filename can be expanded to a full path when the symlinks variable (q.v.) is set to `expand'. Quoting prevents this expansion, and the normalize-path editor command does it on demand. The normalize-command editor command expands commands in PATH into full paths on demand.
Finally, cd and pushd interpret '-' as the old working directory (equivalent to the shell variable owd). This is not a substitution at all, but an abbreviation recognized by only those commands. Nonetheless, it too can be prevented by quoting.


To diff two files, you may often see this:

$ grep somestring file1 > /tmp/a
$ grep somestring file2 > /tmp/b
$ diff /tmp/a /tmp/b

With process substitution this can be simplified into a one liner:

$ diff <(file1) <(file2)

Merge and sort a selection of files:

$ sort -m <(zcat file.1.gz) <(zcat file.2.gz) <(zcat file.3.gz) ... | gzip -c > merged.gz

Related macOS commands

How-to: Redirection and Process Substitution
ht1528 - Enabling and using the 'root' user in macOS.

Copyright © 1999-2024
Some rights reserved