运行与终端分离的程序的方法
1. 简介
作为 Linux 用户,我们经常使用终端来运行各种命令和程序。我们运行的命令在各种情况下需要多一点时间才能完成。在这种情况下,我们可能希望在后台运行该命令,以便终端可以自由地进行其他工作。
或者,我们可能在SSH 会话中远程运行命令,在这种情况下,我们可以方便地在后台启动该进程,然后退出会话。
在本教程中,**我们将介绍几种将进程与终端完全分离的方法。**我们可以使用一些方法在后台启动进程,而一些方法可以帮助我们将已经运行的进程移到后台。
2. 使用 bg、fg和jobs
一旦命令开始运行,**我们可以按 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”的行。