Prev: Expansions, Up: Index, Next: Functions

More command substitution

When Bash evaluates command substitution, it creates a subprocess which executes the command inside and establishes a pipe between the child and the parent process to capture the standard output.

If the command is an external binary, the full process initialization is performed including all things that usually accompany the execution of an external command (copying the environment, loading shared libs etc.).

time for ((i=0; i<10000; i++)); do
    DIR=$(basename "${HOME}")      # External command
done >/dev/null

real    0m10,369s
user    0m7,756s
sys     0m2,920s
If you’re brave enough and curious about the details, try running strace -f bash -c 'DIR=$(basename ${HOME})' and search for system calls: clone, execve.

What would happen if we replaced an external command with its built-in equivalent inside the command substitution?

time for ((i=0; i<10000; i++)); do
    DIR=$(echo "${dir##*/}")     # built-in echo + expansion
done >/dev/null

real    0m4,232s
user    0m3,019s
sys     0m1,489s

The reason for the twofold speed up is that in case of built-in commands there’s no need to perform full initialization, because the code and the shared libraries are already loaded. But wait, isn’t it true that the memory of a process is only accessible by its owner? Normally yes, but…​

Subshell

Command substitution, as well as several other shell constructs, is executed in a subshell. It is a special mode in which the memory of the parent process isn’t copied right away, but rather passed to the child process with copy-on-write semantics — the pages remain shared as long as none of the processes modifies them. This effect can be observed in the following experiment:

$ echo $$
308311
$ (eval 'echo $$')
308311

` is a special parameter which usually stores the ID of the current process (PID). Since `echo` is a built-in command, the first statement prints the PID of the current Bash instance. The second one, however, is a compound command which, just like command substitution, runs in a subshell. To avoid copying the memory, Bash skips re-initialization, so even ` remains unchanged (it can be obtained using $BASHPID).

Nested substitutions

Command substitutions — both with traditional and new syntax — can be nested. Let’s then consider the following example:

$($($($($(:)))))

How many subprocesses exist when : is executed? Use strace to find the answer ;-)

Prev: Expansions, Up: Index, Next: Functions