Understanding Exec in Bash

Bash exec explanation

It changes the current shell's stdout so that it outputs both to the file named file and to the previous stdout (which was presumably a terminal window).

It does not modify stderr, so not all output will be logged.

In general, exec with a set of redirections and no executable applies the redirections to the current shell.

understanding bash exec 1&2 command

exec is a built-in Bash function, so it can have special behavior that an external program couldn't have. In particular, it has the special behavior that:

If COMMAND is not specified, any redirections take effect in the current shell.

(That's quoting from the message given by help exec.)

This applies to any sort of redirection; you can also write, for example, any of these:

exec >tmp.txt
exec >>stdout.log 2>>stderr.log
exec 2>&1

(It does not, however, apply to pipes.)

I don't understand bash exec

Yes, it sends any further output to the file named logfile. In other words, it redirects standard output (also known as stdout) to the file logfile.

Example

Let's start with this script:

$ cat >script.sh
#!/bin/bash
echo First
exec >>logfile
echo Second

If we run the script, we see output from the first but not the second echo statements:

$ bash script.sh
First

The output from the second echo statement went to the file logfile:

$ cat logfile
Second
$

If we had used exec >logfile, then the logfile would be overwritten each time the script was run. Because we used >> instead of >, however, the output will be appended to logfile. For example, if we run it once again:

$ bash script.sh
First
$ cat logfile
Second
Second

Documentation

This is documented in man bash:

exec [-cl] [-a name] [command [arguments]]
If command
is specified, it replaces the shell. No new process is created. The
arguments become the arguments to command. If the -l option is
supplied, the shell places a dash at the beginning of the zeroth
argument passed to command. This is what login(1) does. The -c
option causes command to be executed with an empty environment. If
-a is supplied, the shell passes name as the zeroth argument to the executed command. If command cannot be executed for some reason, a
non-interactive shell exits, unless the execfail shell option is
enabled. In that case, it returns failure. An interactive shell
returns failure if the file cannot be executed. If command is not
specified, any redirections take effect in the current shell, and the
return status is 0.
If there is a redirection error, the return
status is 1. [Emphasis added.]

In your case, no command argument is specified. So, the exec command performs redirections which, in this case, means any further stdout is sent to file logfile.

find command and -exec

The find command has a -exec option. For example:

find / -type f -exec grep -l "bash" {} \;

Other than the similarity in name, the -exec here has absolutely nothing to do with the shell command exec.

The construct -exec grep -l "bash" {} \; tells find to execute the command grep -l "bash" on any files that it finds. This is unrelated to the shell command exec >>logfile which executes nothing but has the effect of redirecting output.

What is the purpose of using exec command

The exec is a builtin command of the Bash shell which allows you to execute a command that completely replaces the current process, i.e., the current shell process is destroyed, and entirely replaced by the command you specify. It is useful when you want to run a command, but you don't want a bash shell to be the parent process. When you exec a command, it replaces bash entirely - no new process is forked, no new PID is created, and all memory controlled by bash is destroyed and overwritten. This can be useful if, for instance, you want to give a user restricted access to a certain command. If the command exits because of an error, the user will not be returned to the privileged shell that executed it. exec may also be used without any command, to redirect all output of the current shell to a file. Here is the definition from man bash:

exec [-cl] [-a name] [command [arguments]]
If command is specified, it replaces the shell. No new process
is created. The arguments become the arguments to command. If
the -l option is supplied, the shell places a dash at the
beginning of the zeroth argument passed to command. This is
what login(1) does. The -c option causes command to be executed
with an empty environment. If -a is supplied, the shell passes
name as the zeroth argument to the executed command. If command
cannot be executed for some reason, a non-interactive shell
exits, unless the exec fail shell option is enabled. In that
case, it returns failure. An interactive shell returns failure
if the file cannot be executed. If command is not specified,
any redirections take effect in the current shell, and the
return status is 0. If there is a redirection error, the return
status is 1.

Understanding exec command

The first exec command should only be used in a script — not at a command line terminal. It replaces the shell with the program foobar, instead of running it as a separate child process. Any commands in the script after the exec foobar will not be executed (even if the shell fails to find foobar to execute); if it is an interactive terminal session, it will report the error and continue.

exec [-cl] [-a name] [command [arguments]]

If command is supplied, it replaces the shell without creating a new process. If the -l option is supplied, the shell places a dash at the beginning of the zeroth argument passed to command. This is what the login program does. The -c option causes command to be executed with an empty environment. If -a is supplied, the shell passes name as the zeroth argument to command. If command cannot be executed for some reason, a non-interactive shell exits, unless the execfail shell option is enabled. In that case, it returns failure. An interactive shell returns failure if the file cannot be executed.

The second exec (with I/O redirection but no command) changes things so that the standard error stream goes to the file /var/log/foobar.log. Any further error messages from the shell, or from commands executed by the shell, go to the log file (unless there's another lot of I/O redirection).

If no command is specified, redirections may be used to affect the current shell environment. If there are no redirection errors, the return status is zero; otherwise the return status is non-zero.

What does this command do? exec bash -l

exec executes a specified command, replacing the current process rather than starting a new subprocess.

If you type

bash -l

at a shell prompt, it will invoke a new shell process (the -l makes it a login shell). If you exit that shell process, you'll be back to your original shell process.

Typing

exec bash -l

means that the new shell process replaces your current shell process. It's probably slightly less resource intensive.

The reason for doing it is probably so that the new shell sets up its environment (by reading your .bashrc, .bash_profile, etc.).

See the bash documentation for more information:

  • Bash Startup Files for how a login shell differs from a non-login shell
  • Bourne Shell Builtins for documentation on the exec command.

(You should be able to read the manual on your own system by typing info bash.)

What are the uses of the exec command in shell scripts?

The exec built-in command mirrors functions in the kernel, there are a family of them based on execve, which is usually called from C.

exec replaces the current program in the current process, without forking a new process. It is not something you would use in every script you write, but it comes in handy on occasion. Here are some scenarios I have used it;

  1. We want the user to run a specific application program without access to the shell. We could change the sign-in program in /etc/passwd, but maybe we want environment setting to be used from start-up files. So, in (say) .profile, the last statement says something like:

     exec appln-program

    so now there is no shell to go back to. Even if appln-program crashes, the end-user cannot get to a shell, because it is not there - the exec replaced it.

  2. We want to use a different shell to the one in /etc/passwd. Stupid as it may seem, some sites do not allow users to alter their sign-in shell. One site I know had everyone start with csh, and everyone just put into their .login (csh start-up file) a call to ksh. While that worked, it left a stray csh process running, and the logout was two stage which could get confusing. So we changed it to exec ksh which just replaced the c-shell program with the korn shell, and made everything simpler (there are other issues with this, such as the fact that the ksh is not a login-shell).

  3. Just to save processes. If we call prog1 -> prog2 -> prog3 -> prog4 etc. and never go back, then make each call an exec. It saves resources (not much, admittedly, unless repeated) and makes shutdown simplier.

You have obviously seen exec used somewhere, perhaps if you showed the code that's bugging you we could justify its use.

Edit: I realised that my answer above is incomplete. There are two uses of exec in shells like ksh and bash - used for opening file descriptors. Here are some examples:

exec 3< thisfile          # open "thisfile" for reading on file descriptor 3
exec 4> thatfile # open "thatfile" for writing on file descriptor 4
exec 8<> tother # open "tother" for reading and writing on fd 8
exec 6>> other # open "other" for appending on file descriptor 6
exec 5<&0 # copy read file descriptor 0 onto file descriptor 5
exec 7>&4 # copy write file descriptor 4 onto 7
exec 3<&- # close the read file descriptor 3
exec 6>&- # close the write file descriptor 6

Note that spacing is very important here. If you place a space between the fd number and the redirection symbol then exec reverts to the original meaning:

  exec 3 < thisfile       # oops, overwrite the current program with command "3"

There are several ways you can use these, on ksh use read -u or print -u, on bash, for example:

read <&3
echo stuff >&4

Need explanations for Linux bash builtin exec command behavior

In this particular case, you have the exec in a pipeline. In order to execute a series of pipeline commands, the shell must initially fork, making a sub-shell. (Specifically it has to create the pipe, then fork, so that everything run "on the left" of the pipe can have its output sent to whatever is "on the right" of the pipe.)

To see that this is in fact what is happening, compare:

{ ls; echo this too; } | cat

with:

{ exec ls; echo this too; } | cat

The former runs ls without leaving the sub-shell, so that this sub-shell is therefore still around to run the echo. The latter runs ls by leaving the sub-shell, which is therefore no longer there to do the echo, and this too is not printed.

(The use of curly-braces { cmd1; cmd2; } normally suppresses the sub-shell fork action that you get with parentheses (cmd1; cmd2), but in the case of a pipe, the fork is "forced", as it were.)

Redirection of the current shell happens only if there is "nothing to run", as it were, after the word exec. Thus, e.g., exec >stdout 4<input 5>>append modifies the current shell, but exec foo >stdout 4<input 5>>append tries to exec command foo. [Note: this is not strictly accurate; see addendum.]

Interestingly, in an interactive shell, after exec foo >output fails because there is no command foo, the shell sticks around, but stdout remains redirected to file output. (You can recover with exec >/dev/tty. In a script, the failure to exec foo terminates the script.)


With a tip of the hat to @Pumbaa80, here's something even more illustrative:

#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2

(note: cat -E is simplified down from my usual cat -vET, which is my handy go-to for "let me see non-printing characters in a recognizable way"). When this script is run, the output from ls has cat -E applied (on Linux this makes end-of-line visible as a $ sign), but the output sent to stdout and stderr (on the remaining two lines) is not redirected. Change the | cat -E to > out and, after the script runs, observe the contents of file out: the final two echos are not in there.

Now change the ls to foo (or some other command that will not be found) and run the script again. This time the output is:

$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr

and the file out now has the contents produced by the first echo line.

This makes what exec "really does" as obvious as possible (but no more obvious, as Albert Einstein did not put it :-) ).

Normally, when the shell goes to execute a "simple command" (see the manual page for the precise definition, but this specifically excludes the commands in a "pipeline"), it prepares any I/O redirection operations specified with <, >, and so on by opening the files needed. Then the shell invokes fork (or some equivalent but more-efficient variant like vfork or clone depending on underlying OS, configuration, etc), and, in the child process, rearranges the open file descriptors (using dup2 calls or equivalent) to achieve the desired final arrangements: > out moves the open descriptor to fd 1—stdout—while 6> out moves the open descriptor to fd 6.

If you specify the exec keyword, though, the shell suppresses the fork step. It does all the file opening and file-descriptor-rearranging as usual, but this time, it affects any and all subsequent commands. Finally, having done all the redirections, the shell attempts to execve() (in the system-call sense) the command, if there is one. If there is no command, or if the execve() call fails and the shell is supposed to continue running (is interactive or you have set execfail), the shell soldiers on. If the execve() succeeds, the shell no longer exists, having been replaced by the new command. If execfail is unset and the shell is not interactive, the shell exits.

(There's also the added complication of the command_not_found_handle shell function: bash's exec seems to suppress running it, based on test results. The exec keyword in general makes the shell not look at its own functions, i.e., if you have a shell function f, running f as a simple command runs the shell function, as does (f) which runs it in a sub-shell, but running (exec f) skips over it.)


As for why ls>out1 ls>out2 creates two files (with or without an exec), this is simple enough: the shell opens each redirection, and then uses dup2 to move the file descriptors. If you have two ordinary > redirects, the shell opens both, moves the first one to fd 1 (stdout), then moves the second one to fd 1 (stdout again), closing the first in the process. Finally, it runs ls ls, because that's what's left after removing the >out1 >out2. As long as there is no file named ls, the ls command complains to stderr, and writes nothing to stdout.



Related Topics



Leave a reply



Submit