Contents

运行与终端分离的程序的方法

1. 简介

作为 Linux 用户,我们经常使用终端来运行各种命令和程序。我们运行的命令在各种情况下需要多一点时间才能完成。在这种情况下,我们可能希望在后台运行该命令,以便终端可以自由地进行其他工作。

或者,我们可能在SSH 会话中远程运行命令,在这种情况下,我们可以方便地在后台启动该进程,然后退出会话。

在本教程中,**我们将介绍几种将进程与终端完全分离的方法。**我们可以使用一些方法在后台启动进程,而一些方法可以帮助我们将已经运行的进程移到后台。

2. 使用 bgfgjobs

一旦命令开始运行,**我们可以按 Ctrl+Z 冻结进程,然后使用 bg 命令在后台恢复它。**然后我们可以使用 jobs 命令查看正在运行的后台进程:

$ sleep 10
^Z
[1]+  Stopped                 sleep 10
$ bg
[1]+ sleep 10 &
$ jobs
[1]+  Running                 sleep 10 &
$ echo
[1]+  Done                    sleep 10

上面的代码片段显示了使用 Ctrl+Z 停止然后使用 bg 移动到后台的进程的输出。运行 jobs命令告诉我们前面的命令确实仍在运行,但在后台。10 秒后运行另一个随机命令(echo)告诉我们初始命令已完成。

一旦我们使用bg命令将进程移至后台 ,就可以使用**fg 命令将其带回前台:**

$ (sleep 11 && echo hello)
^Z
[1]+  Stopped                 ( sleep 11 && echo hello )
$ bg
[1]+ ( sleep 11 && echo hello ) &
$ fg
( sleep 11 && echo hello )
hello

在上面的代码片段中,初始命令应该等待 11 秒并打印“hello”。在我们将其发送到后台并使用fg将其带回前台后 ,我们看到该命令在前台打印“hello”。

3. 使用 & 运算符

我们可以通过在命令末尾附加*“ & ”*来在后台启动命令:

$ gedit &
[1] 968967
$ 
(gedit:968967): GtkSourceView-WARNING **: 16:15:38.174: could not parse color '#bg'
(gedit:968967): GtkSourceView-WARNING **: 16:15:38.175: no color named 'white'
$ echo "running another command"
running another command
$ kill 968967

从上面我们可以看到,在 gedit命令后附加一个 & 号会将其发送到后台并打印其 PID 968967。Gedit 是一个 GUI 程序,它在桌面上打开,同时让终端可以自由运行其他命令。当我们在其 PID 上运行kill 命令时,程序退出。

我们还看到 gedit命令的输出仍然打印在这个终端上。为避免这种情况,我们可以将输出重定向到另一个位置,例如 /dev/null

$ gedit 1>/dev/null 2>/dev/null &
[1] 979813
$ kill 979813
$ echo "hello"
hello
[1]+  Terminated              gedit > /dev/null 2> /dev/null

在这种情况下,我们将输出和错误流重定向到*/dev/null,*我们之前看到的输出现在被抑制了。因此,我们最初只会在终端上收到 PID 通知,然后只有在进程退出时才会收到通知。

4.使用 nohup

**nohup 命令用于以不受“挂断”或终端断开影响的方式运行命令。**当我们使用nohup 启动命令时,该命令会将输出重定向到nohup.out

$ nohup echo hello &
[1] 998797
nohup: ignoring input and appending output to 'nohup.out'
$
^C
[1]+  Done                    nohup echo hello
$ cat nohup.out
hello

在上面的代码片段中,我们使用 nohup启动一个命令,在末尾添加“&”,这样终端就可以自由地运行其他命令。该过程完成后,我们会在nohup.out文件中找到该命令的输出 。

5.使用 disown

我们可以运行一个命令,让终端通过在末尾附加“ *& disown *”来拒绝该进程:

$ echo hello & disown
[1] 1007802
hello

在 disown 之后,我们看到该命令首先打印 PID,然后是程序的输出——它仍然出现在我们的终端上。为了使输出静音,我们可以将其重定向到*/dev/null*,就像我们之前所做的那样:

$ echo hello 1>/dev/null 2>/dev/null & disown
[1] 1016729

现在,该命令只打印 PID 而不是命令的输出。

6. 使用 setsid

当**我们使用 setsid 运行命令时,该命令会在与当前终端断开连接的新会话中启动。**与 disown 和使用“ & ”类似,输出会打印在当前终端上,我们可以通过将其发送到 /dev/null来使其静音:

$ setsid echo hello
hello
$ setsid echo hello 1>/dev/null 2>/dev/null

在后一种情况下,该命令在终端上不打印任何输出。

7. 使用screen

Screen 是一个窗口管理器,它允许我们启动和管理多个虚拟终端。要使用screen在后台运行进程 ,我们可以创建一个新窗口,在那里启动进程并分离窗口。

7.1. 进入屏幕窗口

要进入一个新的屏幕窗口,我们只需输入 screen命令:

$ screen
[screen_window] $ 

进入后,我们可以按 Ctrl+A后跟“(双引号)列出所有活动的屏幕窗口:

[screen_window] 
Ctrl+A " 
 Num Name 
   0 bash

我们看到只有一个屏幕窗口。请注意,“[screen_window]”显示在代码片段中只是为了将其与原始终端区分开来,它实际上并没有打印在输出中。

7.2. 在屏幕窗口中启动命令

现在让我们在屏幕窗口中启动一个长时间运行的命令:

$ watch -n 1 date

这将显示类似于下面的输出,它每秒不断刷新,直到我们使用Ctrl+C 退出:

Every 1.0s: date                          dell: Thu Jun  2 17:13:14 2022
Thursday 02 June 2022 05:13:14 PM IST

7.3. 从屏幕窗口中分离

现在,我们可以从这个屏幕窗口中分离出来,并通过按 Ctrl+A 然后按 d返回到我们原来的终端会话 :

[screen_window}
Ctrl+A d
$ screen
[detached from 1045567.pts-1.dell] 

当我们回到原来的终端时,我们看到了我们之前输入的屏幕命令。我们还在下面看到了一些新的输出,它表示我们从屏幕窗口中分离出来并回到了这里。

7.4. 返回屏幕窗口

我们可以通过使用 -R标志运行screen重新进入我们的屏幕窗口 :

$ screen -R
[screen_window]
Every 1.0s: date                     dell: Thu Jun  2 17:23:05 2022
Thursday 02 June 2022 05:23:05 PM IST

我们看到我们之前启动的watch命令仍在这个屏幕窗口中运行。

7.5. 退出屏幕窗口

要退出此屏幕窗口,我们可以按 Ctrl+C 停止该过程。然后,使用exit 命令关闭窗口:

[screen_window]
Ctrl+C
[screen_window] $ exit
$ screen -R
[screen is terminating]

退出屏幕窗口后,我们回到原来的终端。在这里,我们看到了之前运行的screen -R命令,以及显示屏幕正在终止的输出。

8. 使用 pm2

**对于需要从终端分离程序并使其永久运行的情况,我们可以使用名为pm2 的程序。**这最适合运行需要 24×7 在线并在发生崩溃时自动重启的应用程序服务器或机器人。Pm2负责所有这些。

8.1. 安装 pm2

在安装 pm2 之前,我们需要通过运行以下命令来确保我们已经安装了nodejs和 npm

$ node -v
v14.18.1
$ npm -v
6.14.15

然后我们可以安装pm2

$ sudo npm install -g pm2

8.2. 使用pm2运行程序

我们可以使用 pm2 start 使用 pm2启动 nodejs程序:

$ pm2 start test.js
[PM2] Spawning PM2 daemon with pm2_home=/home/kd/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/kd/tinkering/test.js in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
0  │ test               │ fork     │ 0    │ online    │ 0%       │ 27.5mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
[PM2][WARN] Current process list is not synchronized with saved list. App start_bot differs. Type 'pm2 save' to synchronize.

程序开始后,我们可以通过运行pm2 status查看当前 状态:

$ pm2 status
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ test               │ fork     │ 18   │ online    │ 0%       │ 40.8mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
[PM2][WARN] Current process list is not synchronized with saved list. App start_bot differs. Type 'pm2 save' to synchronize.

虽然 pm2主要是为运行nodejs程序而构建的,但它可以处理其他程序,例如 python 程序:

$ pm2 start test.py
[PM2] Starting /home/kd/tinkering/test.py in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
0  │ test               │ fork     │ 37   │ online    │ 100%     │ 37.3mb   │
1  │ test               │ fork     │ 0    │ online    │ 0%       │ 4.0kb    │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
[PM2][WARN] Current process list is not synchronized with saved list. App start_bot differs. Type 'pm2 save' to synchronize.

我们看到pm2为每个启动的程序分配一个 ID。我们可以使用这个 id 来查看输出或停止程序:

$ pm2 logs 0
[TAILING] Tailing last 15 lines for [0] process (change the value with --lines option)
/home/kd/.pm2/logs/test-error.log last 15 lines:
/home/kd/.pm2/logs/test-out.log last 15 lines:
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
0|test     | hello
$  pm2 stop 0
[PM2] Applying action stopProcessId on app [0](/ids: [ '0' ])
[PM2] [test](/0)┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
0  │ test               │ fork     │ 94   │ stopped   │ 0%       │ 0b       │
1  │ test               │ fork     │ 15   │ errored   │ 0%       │ 0b       │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
[PM2][WARN] Current process list is not synchronized with saved list. App start_bot differs. Type 'pm2 save' to synchronize.

我们编写了两个程序来打印“hello”并退出,而pm2每次退出时都会尝试重新启动它们。因此,我们在输出中看到一连串说“hello”的行。