问题

linux 在终端打开程序后关闭终端,程序也跟着关闭了怎么办?

回答
在 Linux 终端里启动了个程序,结果一关掉终端,程序也跟着消失了,这情况确实挺让人头疼的。咱们今天就来聊聊为啥会这样,以及怎么才能让程序在后台乖乖地运行,就算你关了终端也继续逍遥。

为什么关了终端程序就跟着没了?

这事儿得从 Linux 的一个核心概念说起:进程组 (Process Group) 和 会话 (Session)。

简单来说,你打开终端,其实就是创建了一个会话。而你在终端里输入的命令,比如 `firefox` 或者 `gedit`,它们都会被启动成一个或多个进程。这些进程呢,又会被组织成一个进程组。

默认情况下,你在一个终端会话里启动的所有东西,都属于这个会话里的一个进程组。当你关闭终端窗口时,实际上是给这个终端会话发送了一个 SIGHUP (Hang Up) 信号。这个信号会被传递给会话中的所有进程组的 leader(通常就是你启动的那个程序进程),然后这个进程组就会收到这个信号,并传播给组里的所有进程,最终导致所有这些进程都被终止。

你可以想象成,终端是你和这些程序沟通的“电话线”。一旦你挂断了电话(关闭了终端),那边的程序自然就收不到信号,也就不知道该继续干活了。

怎么样才能让程序在后台跑,不受终端关闭的影响?

别担心,Linux 给咱们提供了几种非常实用的方法来解决这个问题。

方法一:使用 `nohup` 命令

`nohup` 是最直接也是最常用的一个方法。它的名字就挺形象的:“no hang up”,意思就是“不挂断”。

工作原理: `nohup` 会让你启动的命令忽略 SIGHUP 信号。另外,它还会将你程序的标准输出 (stdout) 和标准错误输出 (stderr) 重定向到一个叫做 `nohup.out` 的文件里(或者你指定的文件)。这样一来,即使终端关闭了,程序也能继续运行,并且它的输出信息不会在终端上显示,而是被保存在文件里。

怎么用:

语法非常简单:
```bash
nohup [你的命令] &
```

让我们举个例子,比如你想在后台运行一个叫做 `my_script.sh` 的脚本:
```bash
nohup ./my_script.sh &
```

`nohup`: 告诉系统忽略 SIGHUP 信号。
`./my_script.sh`: 你要执行的命令或脚本。
`&`: 这个符号非常重要!它会将你的命令放到后台运行。如果没有这个 `&`,`nohup` 即使忽略了 SIGHUP,程序仍然会占用你的当前终端,直到你手动中断它。

更进阶的用法: 你可以指定输出文件:
```bash
nohup ./my_script.sh > /path/to/my_output.log 2>&1 &
```
这里的 `> /path/to/my_output.log` 是把标准输出重定向到 `my_output.log` 文件。`2>&1` 是一个非常重要的组合,它表示把标准错误输出 (`2`) 也重定向到标准输出 (`1`) 的位置,也就是同一个文件里。这样,程序的正常输出和错误信息都会被记录下来。

怎么知道程序还在不在跑?

你可以使用 `ps aux | grep [你的程序名]` 来查看进程。比如:
```bash
ps aux | grep my_script.sh
```
如果看到你的脚本在运行,那就说明它成功了。

方法二:使用 `screen` 或 `tmux` (终端多路复用器)

这两种工具可以说是解决这个问题的“终极武器”,它们不仅仅能让你在关闭终端后程序继续运行,还能让你在一个终端里同时管理多个“虚拟终端窗口”,非常强大。

工作原理: `screen` 和 `tmux` 会创建一个独立的“会话”,在这个会话里你可以启动和运行程序。当你从原来的终端退出时,这个独立的会话并不会被终止,里面的程序自然也就继续运行了。你可以随时重新连接到这个会话,就像你从来没有离开过一样。

使用 `screen`:

1. 开始一个新的 screen 会话:
```bash
screen
```
或者给你的会话起个名字:
```bash
screen S my_session_name
```
你现在会进入一个新的、看起来和原来差不多的终端界面,但这个界面实际上已经在一个独立的 `screen` 会话中了。

2. 在新的 screen 会话里启动你的程序:
```bash
./my_script.sh
```
或者
```bash
firefox
```

3. “分离” (detach) screen 会话: 这是关键一步。按下 `Ctrl + a`,然后按 `d` 键。
这时你会回到原来的终端,屏幕上会显示 `[detached from ...] ` 这样的信息,表明你的 `screen` 会话已经被分离,但里面运行的程序还在继续。

4. 重新连接 (attach) 到 screen 会话: 如果你想回来看看或者继续操作,可以使用:
```bash
screen r
```
如果你的电脑上运行着多个 `screen` 会话,你需要指定要连接的会话名或ID:
```bash
screen r my_session_name
```
或者
```bash
screen r [session_id]
```

使用 `tmux`: (个人觉得比 `screen` 更好用一些,但原理类似)

1. 开始一个新的 tmux 会话:
```bash
tmux
```
或者给你的会话起个名字:
```bash
tmux newsession s my_session_name
```

2. 在新的 tmux 会话里启动你的程序:
```bash
./my_script.sh
```

3. “分离” (detach) tmux 会话: 按下 `Ctrl + b`,然后按 `d` 键。
你也会回到原来的终端,看到分离成功的提示。

4. 重新连接 (attach) 到 tmux 会话:
```bash
tmux attachsession
```
或者指定会话名:
```bash
tmux attachsession t my_session_name
```

`screen` 和 `tmux` 的优势:

持久性: 即使服务器断电,只要你的会话还在运行,一旦服务器恢复,你还可以重新连接到之前的会话。
多窗口管理: 你可以在一个 `screen` 或 `tmux` 会话里创建多个窗口,方便地切换和同时查看。
易于管理: 可以给会话命名,方便查找和管理。

方法三:使用 `disown` 命令

`disown` 命令也是用来处理后台进程的,它会将一个正在运行的后台进程从 shell 的作业列表中移除,这样这个进程就不会再收到 shell 发送的 SIGHUP 信号了。

工作原理: 当你用 `command &` 启动一个程序后,它会被放到后台运行,但仍然与当前的 shell 有关联。如果你关闭 shell,shell 会尝试给所有后台作业发送 SIGHUP。`disown` 就是把你从 shell 的作业列表中“划掉”,让它不再管这个进程了。

怎么用:

1. 先用 `&` 把程序放到后台运行:
```bash
./my_script.sh &
```
这时候终端会显示一个进程 ID (PID),比如 `[1] 12345`。这里的 `12345` 就是进程的 PID。

2. 然后使用 `disown` 命令:
如果你只想移除最后一个放在后台的作业(就是上面那个 `[1]`):
```bash
disown
```
如果你想移除特定 PID 的进程(比如刚才的 `12345`):
```bash
disown %1 %1 是指作业号,通常是你启动的第几个后台任务
```
或者直接用 PID:
```bash
disown 12345
```

`disown` 的局限性:

需要先放到后台: 你得先用 `&` 启动程序。
输出处理: `disown` 本身不处理输出重定向,如果你不指定输出文件,程序的输出还是可能往当前终端(虽然你已经退出了)发送,有时候会有点麻烦。通常建议和输出重定向一起使用:
```bash
./my_script.sh > my_output.log 2>&1 &
disown
```

方法四:使用 `systemdrun` (更适合服务化管理)

如果你要运行的是一个需要长期稳定运行的服务,并且你的系统支持 `systemd`(大多数现代 Linux 发行版都支持),那么 `systemdrun` 是一个非常优雅的解决方案。

工作原理: `systemdrun` 可以临时创建一个 `systemd` 服务单元来运行你的命令。`systemd` 是一个强大的系统和服务管理器,它能很好地管理进程的生命周期,即使在终端关闭后也能让程序继续运行。

怎么用:

```bash
systemdrun user scope wait ./my_script.sh
```

或者更简单的,让它在用户服务下运行:
```bash
systemdrun user ./my_script.sh
```

`user`: 表示在用户 session 下运行,而不是作为 root 系统服务。
`scope`: 创建一个 transient scope unit 来运行你的命令。
`wait`: 等待命令执行完成(如果你不加这个,它会立即返回,但你的程序已经在后台运行了)。

使用 `systemdrun` 启动的程序,你可以通过 `systemctl user status [your_script_name].service` 来查看它的状态。

`systemdrun` 的优点:

健壮性: `systemd` 会负责进程的重启和监控。
日志管理: `systemd` 会自动将程序的日志集成到 `journald` 中,方便查看。
资源控制: 可以方便地通过 `systemd` 的特性来限制程序的资源使用。

总结一下选择哪个方法:

临时运行单个命令,需要简单快捷: `nohup ... &` 是个不错的选择。
需要多次重连,管理多个任务,或者在服务器上长时间运行: `screen` 或 `tmux` 是非常强大的工具,强烈推荐学习和使用。
已经有一个后台进程,但想确保它不受终端关闭影响: `disown` 可以派上用场。
要运行的服务化应用,追求稳定和自动化管理: `systemdrun` 是最佳选择。

理解了这些原理和方法,以后你就能在 Linux 终端里随心所欲地管理你的程序了,再也不用担心关个终端程序就跟着“殉情”了!

网友意见

user avatar

在shell退出前,交互的 shell 向所有作业,运行的或停止的,发送SIGHUP信号。shell 向停止的作业发出SIGCONT信号来保证它们会收到SIGHUP

假如想要启动的进程在shell退出之后还可以继续运行,除了tmux和screen有三种方法

  1. setsid命令
  2. nohup命令
  3. disown命令

setsid

setsid命令之所以能让子进程在shell退出后还可运行并不是因为setsid(2)的作用,因为setsid的实现默认是进行一次fork然后让父进程退出,子进程运行exec系列函数,这样用setsid运行的命令就是shell的子子进程,所以不会收到SIGHUP

nohup

nohup使用signal系列函数忽略了SIGHUP信号

disown

disown是个buildin命令使作业不会收到SIGHUP信号

user avatar

现有很多答案都说的不错,不过有一个共同点就是都是告诉你一开始你就打算后台运行的时候怎么做。


作为一个经常掉链子的蠢人,我是来给你送后悔药的。

当你一开始没打算在后台长期执行一个命令,但是运行了之后发现花很多时间,但又不想在那里傻等,怎么办?(这种事通常发生在下班前2小时执行了一个命令,然后跑去撩漂亮的前台姑娘,快下班回来一看还没完,一打听才发现这货居然要执行超过8小时……知识都是在惨痛的教训中获得的!)

首先,你要是还想要程序的输出,那你要么继续傻等,要么停掉现在执行的进程,按照其他答案说的,用nohup或者用tmux/screen一类的工具重新来过。

如果程序的输出无所谓,你只要程序能继续执行下去就好——典型的例子是你压缩一个文件或者编译一个大软件,中途发现需要花很长时间——那你接着看我给你的秘方。

  1. 按下Ctrl-z,让程序进入suspend(这个词中文是——挂起?)状态。
  2. 这个时候你应该会看到 [1]+ Stopped xxxx 这样的输出。
  3. 上面那个 [] 里的数字,我们记为n,然后执行 bg %n ,让暂时停掉的进程在后台运行起来。执行之前,如果不放心,想确认一下,可以用 jobs 命令看看被suspend的任务列表(严格地说,jobs看的不仅仅是suspend的任务,所以,如果你有其他后台任务,也会列出来。)
  4. 然后再执行 disown ,解除你现在的shell跟刚才这个进程的所属关系。这个时候再执行jobs,就看不到那个进程了。
  5. 现在就可以关掉终端,下班回家了。

下面是一个实例,你可以看看。

       bash-3.2$ sleep 3600                                            # 要执行很久的命令 ^Z [1]+  Stopped                 sleep 3600 bash-3.2$ jobs [1]+  Stopped                 sleep 3600 bash-3.2$ bg %1 [1]+ sleep 3600 & bash-3.2$ jobs [1]+  Running                 sleep 3600 & bash-3.2$ disown bash-3.2$ jobs bash-3.2$ ps -ef | grep sleep                                    # 此处输出可知,那个命令还在执行   501 30787 30419   0  6:00PM ttys000    0:00.00 sleep 3600   501 33681 30419   0  6:02PM ttys000    0:00.00 grep sleep bash-3.2$ exit     

此处重启一个终端窗口

       bash-3.2$ ps -ef|grep sleep   501 30787     1   0  6:00PM ??         0:00.00 sleep 3600   501 36701 36592   0  6:05PM ttys001    0:00.00 grep sleep bash-3.2$     

可以看到刚才的命令还在执行。


这个方法,可以让你在后知后觉发现一个命令要执行很久的时候,也可以半路让它改成后台执行。

还有一个好处是,对于一些初期有些需要输入交互的程序,用这个方法在后期长期执行的时候转入后台,可以免去一开始就后台执行导致无法输入信息的烦恼。至于怎么在后台交互(尤其是sudo之类需要输入密码的),那就是另一个问题了……不过,一定要注意,要确定所有交互都结束了再转入后台,不然那个进程会停在等待输入的地方直到被人杀掉或者系统关闭……

但是,这个方法的缺点就是无法获取程序输出了。所以,如果你用bash或者类似兼容的shell,在执行命令后面加一个 |& tee program.log 是个好习惯。随时把标准和错误输出导入到日志文件中,一方面可以防翻页太快缓冲太少,一方面可以半路后悔切后台,还有一个终极作用是可以留下证据防坏人。


哦,对了,提醒一下。如果你不是连的远程服务器,而是本地计算机。那么关了终端窗口,但不要关机,关机了,你的程序还是会停的……(感觉我在说废话……不过,做过各种错事之后,你会发现有些废话还是很重要的。)也不要休眠,休眠了程序就暂停了……虽然不至于彻底灰飞烟灭。


重新看了一眼其他答案,我来补充一下nohup的正确使用方法。(发现现有的某些答案很是蔫损坏……)

       nohup COMMAND > stdout.log 2> stderr.log &     

上面这种,将会把COMMAND命令的标准输出输出到 stdout.log中,错误输出输出到 stderr.log中。

       nohup COMMAND > output.log 2>&1 &     

这个是把标准输出和错误输出都一股脑地输出到output.log文件中。

推荐上面两种写法,自己指定输出文件。如果你确实很懒,那就直接

       nohup COMMAND &     

这个时候,nohup命令会默认把标准输出写入nohup.out文件,文件在你执行命令的路径下,也就是Working Directory。至于错误信息……忘了输出到哪里了,好像是nohup.err还是nohup.error的……因为不常用,不记得了。

之所以不推荐默认,是因为会出现预期外的文件,另外,你要是执行好几个,输出文件里就是一锅粥了……


ps. nohup是no hang up的缩写。hang up就是当你关终端的时候,会发给进程的信号名称(通常记作SIGHUP)。no hang up就是不要发这个信号的意思。

pps. 某答案里的那个 /dev/null 是Linux里的黑洞,啥玩意儿扔进去都没了。所以,你不想要输出的时候,可以用。


至于tmux和screen,这两个东西的使用方法需要一本手册,所以有兴趣的人自己去查吧。(其实是我自己也没记全……就记得了一个Ctrl-B了……)


另外,如果你拥有那台服务器的全权管理权,记得应该有个什么设置,让终端退出的时候可以不发HUP信号。我在一台服务器上见过这种现象,不需要输入 disown ,或者不用nohup,直接后台就可以退出不死(前台程序还是会死的)。但我不知道是怎么设置的,等我哪天有兴趣查明白了再回来补充吧……或者哪位高人评论补充一下。

这个问题我查明白了,回来更新一波。

首先,这个是基于bash来说的,其他shell可能情况不同,目前我也只查了bash的情况。

bash里有一个选项,叫 huponexit 如果这个选项设置成 off ,当且仅当正常退出shell的时候(输入exit命令退出或者以Ctrl-d退出)不会像后台进程发送SIGHUP(hang up),进程会得到保留继续执行。如果是异常掉线,强行关闭终端窗口,则会发送SIGHUP,导致后台进程被杀。

貌似默认的bash是 huponexit=off 的,所以有些答案告诉只要在命令后面加 & 就可以了。就是基于此的。然而,无法防止意外掉线,尤其是网络不稳定的地方,这个事儿很不靠谱。因此,上面的答案还是比较推荐的做法。

再说说这个选项如何查看和设置。

       $ shopt huponexit huponexit       off     

上面这个命令可以查看当前设置,第二行是结果。目前是off。

       $ shopt -s huponexit $ shopt huponexit huponexit       on     

上面这段是把这个选项开启,设置为on,并查看确认。

       $ shopt -u huponexit $ shopt huponexit huponexit       off     

这段则是把这个选项关闭,设置为off,并查看确认。

参考链接:

How bash handles the jobs when logout?

What happens to background jobs after exiting the shell?

In which cases is SIGHUP not sent to a job when you log out?


又想起一个可以完成这个任务的方法,更新补充一下。虽然这个不是专门干这个用的。

可以用 at 命令。at命令原本是用来定时执行任务的,但可以使用 at now 来让任务立刻执行,这个时候执行的进程也是在后台而不依赖于当前shell。

具体写法如下:

       $ at now at> sleep 300 at> <EOT>   # ← 此处按下Ctrl-d结束输入 job 625 at Tue Feb  2 10:24:00 2021 acros800@CY-UATBAT:~ $ ps -ef|grep sleep acros800 23305 23304  0 10:24 ?        00:00:00 sleep 300 acros800 23311 23218  0 10:24 pts/0    00:00:00 grep --color=auto sleep     

任务执行后,会将执行时的输出以邮件的形式发到执行者的服务期内邮箱中,可以用 mail 命令查看。

缺点是,执行的时候的环境可能跟shell下直接执行会有所不同(具体它加载的是什么环境,记不清了……可以用atq来查看在执行或预订执行的列表,然后用at -c JOB_ID来查看具体环境配置及命令)。如果执行的是bash的shell script,可以使用 bash -l SCRIPT_FILE 来带上登录时加载的环境项。



忽然发现被推荐为优质内容了,是不是应该加个广告?

比如……




不过,我都没看过,好不好用,大家自行甄别。实在人不打诳语。

话说回来,开卷有益。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有