1. Overview

In this tutorial, we’ll see the different ways in which we can combine and execute multiple Linux commands efficiently. We’ll be using Bash for our examples, so there could be slight differences with other shells.

2. Why Combine Multiple Commands?

Executing commands one after the other in a command line is a regular activity for a Linux administrator. But, by doing so, we may face the following issues:

  • The first command can take a long time to complete – one has to wait until then to run the second command
  • We may miss a particular command when multiple commands are run one-by-one
  • We may misspell a particular command, which may lead to unintended consequences
  • It’s time-consuming and annoying to run multiple commands one-by-one

To prevent such situations and obtain the expected result, we can combine and execute multiple commands in the command line.

3. Concatenate Commands With “;”

The ;” operator executes all commands regardless of whether the previous ones failed or not.**

After logging into the server, we can execute the following commands:

  • Change to logs directory (using cd)
  • List the latest ten log files (using ls and head)
  • Print the disk usage of logs files (using du)
  • Print the disk usage of all file systems (using df)

Nevertheless, we can concatenate all of the above tasks in a single line using the ; operator:

$ cd logs; ls -lt | head; du -sh ; df -h

However, we need to keep in mind that if one command in the command chain fails, the remaining commands will execute anyways. And this may produce unexpected outputs. 

4. Concatenate Commands Conditionally

The above approach is not suitable for all situations. Such as executing commands based on the success or failure of the previous one. In such cases, we can use one of the following approaches.

4.1. Concatenate Commands With “*&&*“

The “&&” or AND operator executes the second command only if the preceding command succeeds.**

Let’s say we want to change to a new directory using the cd command and echo some message related to the new directory. To do that, one option is to concatenate the commands with “*;*“:

$ pwd
/home

$ cd /tmp; echo "Now we are in $PWD"
Now we are in /tmp

This works fine if the cd command is successful. However, if the cd command fails, for example, if we misspelled the folder name accidentally, the echo command will be executed in the /home directory:

$ pwd
/home

$ cd /invalid-dir; echo "Now we are in $PWD"
bash: cd: /invalid-dir: No such file or directory
Now we are in /home

In our example, the second command is echo. It may print misleading messages but won’t do harm to the system. However, if the second command is a dangerous operation, for instance, rm -rf *, then the whole command becomes: “cd someDir; rm -rf *“. If the cd command fails, the rm command will be executed in the current directory, which is /home in our example.** As a result, all contents of the home directory will be removed. This would be fatal.

To avoid this, we want the second command not to start at all if the first command fails. The solutions is replacing the ; operator with “*&&*“:

$ cd /invalid-dir && echo "Now we are in $PWD"
bash: cd: /invalid-dir: No such file or directory

As we can see, this time, the echo command doesn’t start if the cd command fails.

4.2. Concatenate Commands With “||”

The “||” or OR operator executes the second command only if the precedent command returns an error.

Let’s assume we’re creating a new shell script to archive log files. Before executing a new shell script, we need to make it executable. Let’s see how we can achieve this using the “||” operator.

First, we’ll check if it is executable. If it’s not, we’ll make it executable using the chmod command:

$ [ -x "archive_logs.sh" ] || chmod +x archive_logs.sh

In this example*,* the chmod command executes only if the file archive_logs.sh is not executable.

5. Group Commands

*In Bash, we can use both “{ }*” and “( )” operators to group commands. 

In this section, we’ll address how to use them to group commands and understand the difference between them through examples. Also, we’ll discuss the everyday use-cases of grouping commands.

5.1. Grouping Commands Using “*{ }*“

The syntax to use curly braces “*{ }*” to group commands is:

{ command-list; }

Note that the semicolon (or newline) following the command list is required.

Let’s see an example of grouping four commands using “*{ }*“:

$ { echo "Hi there"; pwd; uptime; date; }
Hi there
/tmp/test/baeldung
 20:27:11 up 30 days,  5:36,  1 user,  load average: 0.26, 0.59, 0.77
Wed 19 Aug 2020 08:27:11 PM CEST

5.2. Grouping Commands Using “*( )*“

The syntax to use parentheses “*( )*” to group commands is quite similar, except that the semicolon following the command list is optional:

( command-list )

Let’s group the same commands using “*( )*“:

$ ( echo "Hi there"; pwd; uptime; date )
Hi there
/tmp/test/baeldung
 20:34:05 up 30 days,  5:43,  1 user,  load average: 0.97, 0.86, 0.81
Wed 19 Aug 2020 08:34:05 PM CEST

5.3. The Difference Between “*{ }” and “( )*“

There is just one single difference between command grouping using “*{ }” and “( )*“:

  • { Commands; } : Commands execute in current shell
  • ( Commands ) : Commands will execute in a subshell

When we change variables in a subshell, the changes are not visible outside the subshell. Let’s see an example to understand the difference between the two grouping methods.

First, let’s group commands using “*{ }*“:

$ VAR="1"; { VAR="2"; echo "Inside group: VAR=$VAR"; }; echo "Outside: VAR=$VAR"
Inside group: VAR=2
Outside: VAR=2

As the output above shows, we initialized a variable: VAR=”1″ and changed its value to “2″ in the group. The variable is updated outside the command group as well.

Secondly, let’s do the same test using “*( )*“:

$ VAR="1"; ( VAR="2"; echo "Inside group: VAR=$VAR" ); echo "Outside: VAR=$VAR"
Inside group: VAR=2
Outside: VAR=1

This time, the variable is changed in a subshell. Therefore, the changes will not affect the outer shell.

Let’s see another example of the cd command:

$ pwd; { cd /etc; pwd; }; pwd
/tmp/test/baeldung
/etc
/etc

$ pwd; ( cd /etc; pwd ); pwd
/tmp/test/baeldung
/etc
/tmp/test/baeldung

In this example, the first output is straightforward. However, in the second output, why was the directory change in “*( )*” not kept?

This is because the cd command sets the environment variable $PWD, and the pwd command reads the variable $PWD. When the cd command updated the variable $PWD in a subshell, the change is not visible in the outer shell.

5.4. When Do We Need to Group Commands?

Primarily, we need to group commands in two scenarios:

  • Apply same redirections on a list of commands
  • Apply logical operators on a list of commands

When commands are grouped, redirections may be applied to the entire command list:

$ ( echo "Hi there"; pwd; uptime; date )  > /tmp/output
$ cat /tmp/output
Hi there
/tmp/test/baeldung
 23:07:59 up 30 days,  8:17,  1 user,  load average: 1.55, 1.95, 2.09
Wed 19 Aug 2020 11:07:59 PM CEST

We’ve learned that we can use && and || to concatenate commands conditionally. With the command group, we can execute a list of commands when the condition is satisfied.

Let’s say, we attempt to ping a website to check if it is alive. Only if the ping command fails, we want to send an SMS to admin and write a log message. To achieve it, we can group the sendSMS command and the writeLog command:

$ ping -c1 "some.website" 1>/dev/null 2>&1 || ( sendSMS && writeLog )

To simulate the execution, we’ll use echo commands to simulate the sendSMS and the writeLog commands, and let them always run successfully:

$ alias sendSMS='echo "sending SMS..(site is down!)..DONE"'
$ alias writeLog='echo "writing logs..(site is down!)..DONE"'

Now let’s do some tests with the command group:

$ ping -c1 "site.can.never.reach" 1>/dev/null 2>&1 || ( sendSMS && writeLog )
sending SMS..(site is down!)..DONE
writing logs..(site is down!)..DONE

$ ping -c1 "www.google.com" 1>/dev/null 2>&1 || ( sendSMS && writeLog )

With the command group, the entire command executes as we expected, no matter whether the ping command succeeded or failed.

Next, let’s see what will happen if we don’t group the sendSMS and writeLog commands:

$ ping -c1 "site.can.never.reach" 1>/dev/null 2>&1 || sendSMS && writeLog 
sending SMS..(site is down!)..DONE
writing logs..(site is down!)..DONE

$ ping -c1 "www.google.com" 1>/dev/null 2>&1 || sendSMS && writeLog 
writing logs..(site is down!)..DONE

As the output above shows, without grouping the sendSMS and writeLog commands, if the site is down, the whole command works as we expect.

However, if the ping command succeeds, the writeLog command is executed anyway. This isn’t what we want.

Therefore, grouping commands may help us solve some problems neatly.

6. Multiple Commands in Background

To execute a single command in the background mode, we can use the “*&*” operator. But to execute multiple commands in the background, we can use one of two ways:

  • Use “*&” along with “&&*“
  • Use “*&*” along with command group

Let’s say we have a shell script, let’s call it execute_backup_db.sh, that backs up the application database. We need to check how much time this shell script takes to complete. Let’s use the date command before and after the execution of the shell script to get the total execution time. This shell script may take a long time to complete.

So let’s run this in the background using the & operator and log the command output in a log file:

$ date; ./execute_backup_db.sh; date & > execute_backup_db.log

Using **&** with “*;*” will only run the final date command in the background. But the first date command and the shell script run only in the foreground. Instead, to run all commands in background conditionally, let’s do:

$ date && ./execute_backup_db.sh && date & > execute_backup_db.log

To achieve the same with “*;*“, let’s use the “( )” operator:

$ (date ; ./execute_backup_db.sh ; date) & > execute_backup_db.log

7. Conclusion

In this tutorial, we saw the different ways in which we can combine and execute multiple commands in the command line efficiently.

Operator ; can be used if the execution of multiple commands is unconditional. Whereas operators “*&&” and “||*” can be used if the execution of the command is based on the success or failure of the previous command.

Moreover, we addressed two ways to group commands and discussed the difference between them.

Also, we saw how we could execute multiple commands in the foreground and the background.

However, to regularly run multiple commands in a particular order, we may need to put all those commands in a shell script.


» 下一篇: 命令替换