1. Introduction
Let's unlock the power of bash scripting with this guide on exec command. Deepen your understanding of process management, command execution, and input/output redirection from the command line.
2. Understanding the exec Command
Generally, when we execute a command in the Bash shell, it spawns a new child process and runs the command in the context of this process. But, when running a command using exec, it replaces the current shell process with a new process. The control never return to the shell script.
So you may think what fork does? both fork and exec are used to create processes. But both work differently. Basically, when a process calls fork it duplicates itself to create a child process whereas exec doesn't create a new process but simply replaces the current one. It's very common that many applications use it together.
Let's look into the basic syntax of exec:
exec [command [arguments]]
Where
[command [arguments]]
: The command exec to execute. If any argument command has it can be passed. If the command is not used exec can be used to redirect the shell's input or output.
Overview of exec with redirection
Command | Description |
---|---|
Redirect standard output to a file | exec > file.txt |
Redirecting standard error to a file | exec 2> errorfile.txt |
Redirecting both standard output and standard error to a file | exec >outanderrorfile.txt 2>&1 |
Open a file for reading | exec 3< datefile.txt |
3. Use Cases of exec command
Let's look deep dive into some use cases of the bash exec command with examples.
3.1 Using exec to Execute Commands
When a bash command is executed, generally it invokes the combination of fork() and exec(). Internally the parent will first fork to create a child process and then the child process will use exec to replace itself with the new program.
Let’s try to understand this with an example:
Executing ps command to get the list of currently running processes.
$ ps
PID TTY TIME CMD
1002 pts/2 00:00:00 bash
1101 pts/2 00:00:00 ps
The process id of the current shell is 1002.
Let’s execute a sleep command in this shell and observe the process tree.
$ sleep 10
$ ps -ef --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:44 ? 00:00:01 /sbin/init
root 2 1 0 13:44 ? 00:00:00 /init
root 368 367 0 13:44 ? 00:00:00 | \_ /init
linuxopsys 1002 888 0 15:01 pts/2 00:00:00 | \_ -bash
linuxopsys 1094 1002 0 15:05 pts/2 00:00:00 | \_ sleep 10
linuxopsys 1036 888 0 15:03 pts/3 00:00:00 | \_ -bash
linuxopsys 1095 1036 0 15:05 pts/3 00:00:00 | \_ ps -ef --forest
Observe the process id of the sleep command. A new child process that has PID = 1094 is spawned which executes the command.
But, when we execute commands using exec, it simply invokes the exec() loading the passed command in the currently running process.
Let’s run the sleep command in exec mode this time:
$ exec sleep 10
Observing the process tree once again:
$ ps -ef --forest
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:44 ? 00:00:01 /sbin/init
root 2 1 0 13:44 ? 00:00:00 /init
root 368 367 0 13:44 ? 00:00:00 | \_ /init
linuxopsys 1002 888 0 15:01 pts/2 00:00:00 | \_ sleep 10
linuxopsys 1036 888 0 15:03 pts/3 00:00:00 | \_ -bash
linuxopsys 1108 1036 0 15:08 pts/3 00:00:00 | \_ ps -ef --forest
This time we see that the sleep command replaces the bash command itself in the process 1002.
From this, we can understand the parent process can continue with other tasks while the child executes new programs.
3.2. Using exec command to replace the login shell
Suppose our default login shell is bash and we want to replace it with sh, this can be done by using the exec command.
Example:
$ cat ~/.bashrc
# .bashrc
## Switch to sh
exec sh
Here in the example the exec sh
command in a ~/.bashrc
file replaces the current Bash shell with a Bourne shell (sh) every time a new non-login interactive Bash shell is started.
Sourcing .bashrc to reflect the changes in this session.
$ source ~/.bashrc
3.4. Invoking another program from a shell script
Let’s see how we can use the exec command to call other programs from a shell script and let it execute in the same session.
#!/bin/bash
function invokeScriptAndReturn() {
./listFilesInDirectory.sh
echo "Executing in function invokeScriptAndReturn"
}
function invokeScriptAndTerminate() {
exec ./listFilesInDirectory.sh
echo "Executing in function invokeScriptAndTerminate"
}
invokeScriptAndReturn
invokeScriptAndTerminate
The main difference between the two functions is that invokeScriptAndReturn will continue execution of the script after listFilesInDirectory.sh completes, whereas invokeScriptAndTerminate will replace the current process with listFilesInDirectory.sh, and the rest of the script will not be executed.
3.5. Using exec with find command
The exec command can be used in conjunction with find to execute other operations (mv, cat, cp, rm, grep, wc, sed, etc) on each file that matches the search criteria.
Example exec with find:
Let's assume we have the following files in the current directory.
$ ls
invokeProgramUsingExec.sh listFilesInDirectory.sh txtFile1.txt txtFile2.txt
$ cat txtFile1.txt
Bag of words:
Slow, Fast, Quick, Rapid, Brisk, Speedy, Abrupt, Sharp, Instant, Relaxed, Easy, Unhurried
$ cat txtFile2.txt
Bag of words:
Sushi, Rice, Wedges, Noodles, Pizza, Burger, Pasta, Chicken, Egg
The following command search through files in the current directory and its subdirectories and specifically looks for files whose names start with "txtFiletxtFile", and within those files, it searches for words that start with 's' (case insensitive).
$ find . -name "txtFiletxtFile*" -exec grep -iwoe "s[a-zA-Z]*" {} \;
Slow
Speedy
Sharp
Sushi
3.6. Using exec to redirect stdin, stdout, and stderr
With the exec command, we can also modify the file descriptors and redirect logs to the text files instead of stdout. This comes in very handy during debugging especially when dealing with large scripts.
Read from a file by changing stdin
Let’s take an example to understand how we can manipulate stdin to reference and take inputs from a file instead of the terminal.
Reusing the text file from the previous example.
#!/bin/bash
exec < txtFile1.txt
i=0
while read line;
do
echo "Line $i: $line"
i=$((i+1))
done
exec < txtFile1.txt tells the Linux to take inputs from the text file instead of the terminal. Within the loop, we iterate over the file and print it to stdout.
Write to a file by redirecting stdout and stderr
Similar to stdin, stdout, and stderr can also be manipulated to redirect the output to a file instead of the terminal.
See the following example:
#!/bin/bash
outFile="outTxtFile.txt"
exec 1>$outFile
echo "Text from txtFile1.txt:-"
while read -r line;
do
echo "Line $i: $line"
done < txtFile1.txt
echo "Text from txtFile2.txt:-"
while read -r line;
do
echo "Line $i: $line"
done < txtFile2.txt
exec 2>&1
echo "Following line will throw an error that gets added to the output file"
dsfd
- We create an output file and use exec 1> command to redirect the stdout to this file.
- Next, we iterate over the text files and echo the output to stdout.
- We also redirect stderr to stdout by using the command exec 2>&1.
- We run an invalid command dsfd. Bash recognizes this and outputs the error to the stdout.
3.7. Restoring the file descriptors
Modifying the file descriptors is a useful tool but we should always take care to revert it back to the original state once done.
Let’s see how we can do this:
#!/bin/bash
exec 11<"txtFile1.txt"
exec 12>&1
exec > "outTxtFile2.txt"
echo "This output goes in outTxtFile2.txt"
while read -u 11 line
do
echo $line
done
exec 11<&-
exec 12>&-
- exec 11<"txtfile1.txt" opens a file descriptor 11 and references a text file.
- exec 12>&1 opens a file descriptor 12 and references the stdout.
- exec > "outTxtFile2.txt" finally tells the shell to redirect stdout to the output file.
- We iterate over the file descriptor 11, read lines from the input file, and output it to the output file
- instead of stdout.
- Finally, we close the input and the output file descriptors 11, and 12 using the command exec
- 11<&- and exec 12>&- respectively.
Comments