如何在Linux中关闭SSH客户端后防止后台进程被停止

308

我正在通过SSH(Putty)在Linux机器上工作。 我需要在晚上期间让一个进程一直运行,所以我认为我可以在命令末尾使用&符号将进程放入后台并将stdout重定向到文件中。

令我惊讶的是,这并不起作用。 一旦关闭Putty窗口,进程就会停止。

我该如何防止这种情况发生?


如果您想强制会话保持打开状态,请参阅https://dev59.com/4F8f5IYBdhLWcg3wD_Av - tripleee
20个回答

314

请查看"nohup"程序。


4
如何在事后停止它? - Derek Dahmer
9
登录后执行“kill <pid>”命令。如果您不知道pid,请使用“pidof”命令。 - JesperE
38
您可以使用 nohup command > /dev/null 2>&1 & 命令在后台运行程序,且不会生成任何标准输出或错误输出(也不会产生 nohup.out 文件)。 - KCD
如果我需要提供一些输入怎么办?例如,我有一个长时间运行的脚本需要在后台运行,但它首先要求我的FTP密码。在这种情况下,nohup无法帮助。是否有一种方法可以通过Ctrl+Z/bg来解决问题? - Sergey
1
由于我很懒,也不擅长记忆加密的字符序列,所以我根据@KCD的建议编写了这个,并经常使用它。 - Anomaly
1
这在我的电脑上不起作用,因为它不能保护程序在关闭ctty时不受到SIGHUP的影响。我有一个程序,它绝不能接收到SIGHUP信号。nohup无法解决问题,因为它不能防止SIGHUP信号,只是默认忽略它,这并不总是有效。由于我没有screentmuxat或类似的工具,我需要一种在shell级别上确保将程序与ctty分离的方法。我找到的唯一方法是使用ssh -T remote '(program&)&'启动程序的hack方式,这使得在交互式的ssh会话中无法将程序放入后台。 - Tino

171

我建议使用GNU Screen。它可以让你断开与服务器的连接,同时所有进程仍然在运行。在我了解它之前,我不知道该怎么生活。


7
这是我使用过的最棒的软件之一。说真的。我在一个BSD盒子上运行它,然后从任何地方都可以通过ssh连接到它,只需要重新连接到我的屏幕,就可以看到我正在进行各种操作的所有终端。 - Adam Jaskiewicz
1
我可以证实这一点。Screen是一个很棒的应用程序。重新附加会话的能力非常惊人,可以节省很多可能会丢失的工作。 - willasaywhat
17
取决于你是否需要重新连接到后台应用程序。如果需要,那么使用screen是唯一的选择。但是,如果是一次性使用,那么nohup同样可以很好地胜任任务,甚至更好。 - Dave Sherohman
1
+1 用于屏幕。或者,作为替代方案,使用tmux(我比screen更喜欢这个)甚至是byobu,它是screen或tmux的一个不错的前端。您只需键入screen即可获得要使用并稍后返回的shell,或者使用screen运行命令,例如“screen command”:只要进程“command”存在,屏幕会话就会存在,并且如果它是非常长的内容,则可以随时返回并查看其标准输出。 - gerlos
2
Linode网站提供了一个很好的介绍,介绍如何使用screen - mabalenk
显示剩余4条评论

82

当会话关闭时,进程将收到SIGHUP信号,但显然未捕获。您可以在启动进程时使用nohup命令或在启动进程后使用bash内置命令disown -h来防止发生这种情况:

> help disown
disown: disown [-h] [-ar] [jobspec ...]
     By default, removes each JOBSPEC argument from the table of active jobs.
    If the -h option is given, the job is not removed from the table, but is
    marked so that SIGHUP is not sent to the job if the shell receives a
    SIGHUP.  The -a option, when JOBSPEC is not supplied, means to remove all
    jobs from the job table; the -r option means to remove only running jobs.

4
这里的优点是 disown 命令适用于已经启动的进程。 - Christian K.
1
“jobspec” 是指进程 ID 吗? - Stewart
1
没问题,我在这里找到了答案:https://dev59.com/SHRB5IYBdhLWcg3wbGxB - Stewart

45

daemonize?nohup?SCREEN?(tmux ftw,screen is junk ;-))

自从应用程序起源以来,只要像其他应用程序一样做就可以了——双重fork。

# ((exec sleep 30)&)
# grep PPid /proc/`pgrep sleep`/status
PPid:   1
# jobs
# disown
bash: disown: current: no such job

嘭!完成了 :-) 我在各种应用程序和老机器上无数次地使用了这个。您可以与重定向等结合使用,以在您和进程之间创建一个私有通道。

将其创建为coproc.sh:

#!/bin/bash

IFS=

run_in_coproc () {
    echo "coproc[$1] -> main"
    read -r; echo $REPLY
}

# dynamic-coprocess-generator. nice.
_coproc () {
    local i o e n=${1//[^A-Za-z0-9_]}; shift
    exec {i}<> <(:) {o}<> >(:) {e}<> >(:)
. /dev/stdin <<COPROC "${@}"
    (("\$@")&) <&$i >&$o 2>&$e
    $n=( $o $i $e )
COPROC
}

# pi-rads-of-awesome?
for x in {0..5}; do
    _coproc COPROC$x run_in_coproc $x
    declare -p COPROC$x
done

for x in COPROC{0..5}; do
. /dev/stdin <<RUN
    read -r -u \${$x[0]}; echo \$REPLY
    echo "$x <- main" >&\${$x[1]}
    read -r -u \${$x[0]}; echo \$REPLY
RUN
done

然后

# ./coproc.sh 
declare -a COPROC0='([0]="21" [1]="16" [2]="23")'
declare -a COPROC1='([0]="24" [1]="19" [2]="26")'
declare -a COPROC2='([0]="27" [1]="22" [2]="29")'
declare -a COPROC3='([0]="30" [1]="25" [2]="32")'
declare -a COPROC4='([0]="33" [1]="28" [2]="35")'
declare -a COPROC5='([0]="36" [1]="31" [2]="38")'
coproc[0] -> main
COPROC0 <- main
coproc[1] -> main
COPROC1 <- main
coproc[2] -> main
COPROC2 <- main
coproc[3] -> main
COPROC3 <- main
coproc[4] -> main
COPROC4 <- main
coproc[5] -> main
COPROC5 <- main

然后你就可以生成任何内容了。<(:) 通过进程替换打开一个匿名管道,这个进程会死亡,但是由于你仍然持有管道的句柄,所以管道仍然存在。我通常使用 sleep 1 而不是 :,因为它略微有点竞态条件,并且如果运行一个真正的命令(例如 command true),则永远不会发生“文件忙”错误。

“heredoc sourcing”:

. /dev/stdin <<EOF
[...]
EOF

这适用于我尝试过的每个 shell,包括 busybox/etc (initramfs)。我以前从未见过这样做,是在探索时独立发现的,谁知道源码可以接受参数?但如果有这样的东西存在,它通常作为一种更可管理的 eval 形式。


3
为什么要踩这篇内容呢?即使这个问题很老,显然仍然有价值,因为还有11个答案都很差。这个解决方案是30年来惯用的、被接受的守护进程方式,而不是无意义的应用程序比如nohup等。此外,它没有使用systemd。 - anthonyrisinger
7
无论你的回答有多好,有时在 Stack Overflow 上会有人不喜欢它并进行了负评。最好不要过于担心这个问题。 - Alex D
1
@tbc0...尝试使用ssh myhost "((exec sleep 500)&) >/dev/null" - anthonyrisinger
1
@anthonyrisinger 好的,可以运行。我认为这更简洁:ssh myhost 'sleep 500 >&- 2>&- <&- &'TMTOWTDI ;) - tbc0
1
这太棒了。这是唯一在busybox中实际起作用的解决方案。它应该得到更多的赞同。 - Hamy
显示剩余2条评论

38
nohup blah &

用你的进程名称替换blah!


2
你可能想要添加重定向标准输出和标准错误。 - David Nehme
9
nohup 命令会将标准输出和标准错误输出重定向到 nohup.out 文件(或者根据版本可能是 nohup.out 和 nohup.err 文件),所以除非你需要运行多个命令,否则不需要使用它。 - Chas. Owens

17

就我个人而言,我喜欢使用“批处理”命令。

$ batch
> mycommand -x arg1 -y arg2 -z arg3
> ^D

这个东西将其置于后台,然后将结果发送给您。它是cron的一部分。


12
正如其他人所指出的那样,要在后台运行进程以便您可以断开SSH会话,您需要使后台进程正确地与其控制终端脱离关联 - 这是SSH会话使用的伪终端。
您可以在《Stevens的“高级网络程序设计,第1卷,第3版”或Rochkind的“高级Unix编程”等书籍中找到有关守护进程的信息。
最近几年,我不得不处理一个不正确地守护进程的顽固程序。最终我创建了一个通用的守护进程程序 - 类似于nohup但具有更多可用的控件。
Usage: daemonize [-abchptxV][-d dir][-e err][-i in][-o out][-s sigs][-k fds][-m umask] -- command [args...]
  -V          print version and exit
  -a          output files in append mode (O_APPEND)
  -b          both output and error go to output file
  -c          create output files (O_CREAT)
  -d dir      change to given directory
  -e file     error file (standard error - /dev/null)
  -h          print help and exit
  -i file     input file (standard input - /dev/null)
  -k fd-list  keep file descriptors listed open
  -m umask    set umask (octal)
  -o file     output file (standard output - /dev/null)
  -s sig-list ignore signal numbers
  -t          truncate output files (O_TRUNC)
  -p          print daemon PID on original stdout
  -x          output files must be new (O_EXCL)

双破折号在不使用GNU getopt()函数的系统上是可选的;在Linux等系统上,它是必需的(或者您必须在环境中指定POSIXLY_CORRECT)。由于双破折号在任何地方都有效,因此最好使用它。
如果您需要daemonize的源代码,请仍然可以通过发送电子邮件联系我(firstname dot lastname at gmail dot com)。
但是,该代码现在(终于)在我的SOQ(Stack Overflow Questions)存储库中作为文件daemonize-1.10.tgzpackages子目录中提供。

13
为什么不将源代码放在 Github 或 Bitbucket 上? - Rob
6
为什么源代码不在Github上会导致一个踩的评价? - Jonathan Leffler
7
在我看来,列出一个程序的所有酷炫选项,但这个程序没有以任何形式公开(甚至不是商业发布),这几乎是在浪费读者的时间。 - DepressedDaniel

12

对于大多数进程,您可以使用这个古老的Linux命令行技巧来伪守护程序:

# ((mycommand &)&)
例如:
# ((sleep 30 &)&)
# exit

然后打开一个新的终端窗口并输入以下命令:

# ps aux | grep sleep

将展示sleep 30仍在运行。

你所做的是将进程作为子进程的子进程启动,当你退出时,通常会触发进程退出的nohup命令不会向下级传递到孙子进程,使其成为一个孤儿进程,仍然在运行。

我更喜欢这种"设置并忘记"的方法,无需处理nohupscreen、tmux、I/O重定向或任何那些东西。


7
在Debian系统上(远程机器上) 安装: sudo apt-get install tmux 用法: tmux 运行您想要的命令 重命名会话: Ctrl+B,然后 $ 设置名称 退出会话: Ctrl+B,然后 D (这将离开tmux会话)。 然后,您可以注销SSH。 当您需要再次回来/检查它时,启动SSH并输入 tmux attach session_name 它会带您回到您的tmux会话。

这是正确的方法。 - kilgoretrout

5

nohup非常适合将详细信息记录到文件中。但是,当它进入后台时,如果您的脚本要求密码,则无法为其提供密码。我认为您应该尝试screen。这是一个实用程序,您可以使用yum在Linux发行版上安装,例如在CentOS上使用yum install screen,然后通过putty或其他软件访问您的服务器,在shell中键入screen。它将在putty中打开screen [0]。完成您的工作。您可以在同一putty会话中创建更多的screen[1],screen[2]等。

您需要了解的基本命令:

启动screen

screen


创建下一个screen

ctrl+a+c


移动到您创建的下一个screen

ctrl+a+n


分离

ctrl+a+d


在工作期间关闭putty。下次通过putty登录时输入

screen -r

重新连接到您的screen,您可以看到进程仍在screen上运行。要退出screen,请键入#exit。

有关更多详细信息,请参见man screen


假设 yum 是正确的工具,但当你不知道发行版时,使用它并不好。你应该明确哪些发行版可以使用 yum 安装 screen - tymik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接