Project 85. Take Advantage of Subshells"How do I force a group of commands to execute in their own environment?" This project discusses the use of subshells: what they are and how you might take advantage of their special features. It also introduces group commands, which are similar to subshells. Tip
SubshellsA subshell is a new instance of a shell launched to run a single command, a command list (one or more commands separated by a semicolon), or a shell script. Learn More
To execute a command list in a subshell, enclose it in parentheses on the command line. $ (cd /; ls) This technique produces similar results to executing the command list in the normal manner except for one important difference: Because the command list runs in a new shell instance and not the current shell, it executes in a new environment. Recall that a new shell instance inherits environment variables from the current interactive shell, but not other settings, such as shell variables, attributes, and options. Further, no part of the subshell's environment is passed back to the parent shell. In our simple example, then, the built-in cd command executed in a subshell can't change the interactive shell's current working directory. Tip
Create Local Blocks of CodeThe previous section explained that subshells execute in their own environments. We can take advantage of this when writing shell scripts. Enclosing a section of script code within parentheses, so that it executes in its own subshell, lets us set local shell variables and attributes that apply only to the enclosed code block. Such settings are not visible outside the code block and do not affect the remainder of the script when the code block has completed executing. Learn More
Learn More
Here's a neat trick that uses a subshell to localize shell attributes. Suppose that we need to comment out a section of code and choose to use the noexec attribute to do so. Here's our first attempt, which doesn't work; line 3 is never echoed. $ cat block-eg #!/bin/bash echo line 1 set -o noexec # switch off execution to comment out echo line 2 set +o noexec # switch execution back on echo line 3 $ ./block-eg line 1 The reason for the script's failure lies in the fact that the set +o noexec statement is never executed; we just switched off execution, and this includes execution of the built-in set command. We get around this problem by placing the code to comment out in a subshell. Shell attributes set in a subshellset -o noexec, in this exampleare not passed back to the parent shell, so we don't need to turn execution back on. Clever! $ cat block-eg #!/bin/bash echo line 1 (set -o noexec echo line 2) echo line3$ ./block-eg line 1 line 3 Selectively Redirect Input and OutputWe can group commands and apply selective input and output redirection. We might discard the standard error from several commands by writing them as a subshell. This technique averts the necessity to redirect the standard error individually from every command in the group. $ cat redir-eg #!/bin/bash dir=$1; file=$2 ( cd $dir ls $file ) 2> junk # more-commands... Here's another example that uses a subshell to redirect the standard input of a group of commands. The main script reads its input (name and age) from the terminal; the parenthesized section takes its input (code and membership) from the file autodata. $ cat eg #!/bin/bash read -p "Name: " name ( read code read membership echo "Code: $code, membership: $membership" ) < autodata.txt read -p "Age: " age echo "Name: $name, age: $age" The subshell reads from the file autodata.txt. $ cat autodata.txt ABC 123 When we run the script, we provide a name and (false ) age. $ ./eg Name: Adrian Code: ABC, membership: 123 Age: 21 Name: Adrian, age: 21 As the script stands, the values read from the file autodata are lost when the subshell completes; the local shell variables code and membership are not passed back to the parent shell. Although this limitation stems from the rather simple example constructed to illustrate subshells, it provides a platform to illustrate some useful tricks. The next code extracts shows you how to pass values back from a subshell to the main shell. New and changed lines are shown in bold. $ cat eg #!/bin/bash declare -a autodata read -p "Name: " name autodata=($( ( read code read membership echo "$code $membership" ) < autodata.txt )) read -p "Age: " age echo "Name: $name, age: $age" echo "Code: ${autodata[0]}, membership: ${autodata[1]}" When we run the script, we see that the code and membership values are passed back to, and displayed from, the main script. $ ./eg Name: Adrian Age: 21 Name: Adrian, age: 21 Code: ABC, membership: 123 How does this work? The whole subshell runs as a subcommand (enclosed in $(...)). As with all subcommands, Bash ultimately reads this expression as the value of its outputin this case, the value of subshell variables code and membership, which are echoed by the subshell before it completes. Furthermore, we capture that value (before the subshell disappears) by assigning the output of the subcommand to an array variable, autodata, using the expression autodata=(value) Learn More
Learn More
where value is the subshell run as a subcommand. This example employs a few techniques, and you might have to experiment a little to follow how it works. Learn More
Redirect stdout with TschThe Tcsh shell is not able to redirect standard error independent of standard output. The Bash shell uses the following syntax to redirect only standard error. cmd 2> file In the Tcsh shell, we must apply the following trick. (cmd >/dev/tty) >& file Note
The command is run in a subshell, and standard output is redirected back to the terminal. This has no effect except that the output from the subshell now contains only standard error. Then we specify Tcsh shell syntax to redirect both standard output (there's none, as it has already been redirected) and standard error to the file file. Tip
Group CommandsA group command is like a subshell. To form a group command, enclose a command list, or a section of a shell script, in braces. { command; command; ...;} The difference between a subshell and a group command is that the current shell, not a new instance of the shell, executes a group command. This means that it does not execute in its own local environment, so some of the tricks employed using subshells do not work. Note
Let's revisit the standard-input example that we used earlier when illustrating subshells. In our new version, we employ a group command instead of a subshell. We need no longer use clever trickery to preserve the value of the variables code and membership, because they are no longer local to the enclosed block of code. $ cat eg-group #!/bin/bash read -p "Name: " name { read code read membership } < autodata.txt read -p "Age: " age echo "Name: $name, age: $age" echo "Code: $code, membership: $membership" $ ./eg-group Name: Adrian Age: 21 Name: Adrian, age: 21 Code: ABC, membership: 123 Project 81 shows how Bash operators are used outside a conditional expression to make execution of a second command dependent on the outcome of a first. For example: $ command1 && command2 The command2 is executed if, and only if, command1 returns TRUE. This technique works because Bash does not evaluate the second part of an AND statement if the first part is FALSE. (There's no need to; if the first part returns FALSE, the result of the entire AND expression can only ever be FALSE.) This behavior is known as short-circuiting. If either command is or both commands are a command sequence, you must make the sequence a group command for this technique to work. $ { cmd1; cmd2; ...; } && { cmd3; cmd4; ...;} Here's a useful trick that asks for root authentication. sudo -p "Admin password " echo 2> /dev/null || ¬ { echo "Incorrect"; exit; } The code is useful when placed at the start of a script that, later on, issues commands that require root permission obtained via sudo. The sequence will prompt for an administrator's password as soon as the script is invoked. Authentication resulting from a correct password lasts 5 minutesplenty of time for most homemade scripts to run. If authentication fails, the second (group) command displays an error message and the script exits, before needless execution of the code that precedes an internal sudo command. |