Project85.Take Advantage of Subshells


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

Use the environment variable SHLVL to discover how deeply nested the current (sub-)shell instance is. Level 1 is the login shell, level 2 is a subshell, level 3 is a subshell launched by the subshell, and so on.


Subshells

A 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

Project 4 includes a section explaining shell and environment variables and their respective scopes.


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

A script is run by the executable named in the first line of the shell scriptusually, #!/bin/bash. The first line of a script may name any executable, not necessarily a shell. Here's a (pointless) illustration.

$ cat myecho #!/bin/echo $ ./myecho Hello there! ./myecho Hello there!



Create Local Blocks of Code

The 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

Project 82 talks about the noexec attribute.


Learn More

Project 86 shows you how to use a subshell to limit the scope of a signal handler in a Bash script.


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 Output

We 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

Project 87 covers Bash array variables.


Learn More

Project 55 shows how to launch commands to run in the background.


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

Project 6 covers redirection.


Redirect stdout with Tsch

The 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 syntax to express a group command is quite fussy. The opening brace must be followed by a space, and a semicolon must terminate the last command.


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

A group command executes more efficiently than a subshell and is preferable whenever possible.


Group Commands

A 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

You can think of a group command as being an inline and anonymous (unnamed) function.


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.




Mac OS X UNIX 101 Byte-Sized Projects
Mac OS X Unix 101 Byte-Sized Projects
ISBN: 0321374118
EAN: 2147483647
Year: 2003
Pages: 153
Authors: Adrian Mayo

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net