JAVA通过system.in获取标准输入与NOHUP、&的使用分析

这篇博客分析了在JAVA程序中使用system.in获取标准输入时,结合nohup和&命令的行为。nohup会将标准输入重定向到/dev/null,导致读取输入时抛出异常,而&则使程序在后台运行,但不改变标准输入。当两者结合使用时,虽然程序可以避免SIGHUP信号影响,但仍会因输入重定向问题而出现异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA通过system.in获取标准输入与NOHUP、&的使用分析

前情提要

昨天同事问到,如果jar中包含通过system.in获取标准输入的代码时,这个jar包是否可以配合nohup使用?这个问题第一感觉是可以,因为nohup让程序可以永久的执行下去,即便关闭终端,jar依然可以运行,只是无法获取标准输入了。然而仔细研究下去,发现真实情况和预想的有些出入。

测试代码

首先写一个java程序来作为我们实验的素材:

package my.in;
import java.io.IOException;
public class InTest {
	public static void main(String[] args) throws IOException {
		for(;;) {
			System.out.print("Enter a Char:"); 
		    char i = (char) System.in.read(); 
		    System.out.println("your char is :"+i); 
		}
	}
}

我们从终端读取输入并输出。打个JAR包来试验一下:

$ java -jar java-in-test.jar
Enter a Char:a
your char is :a

运行情况良好,然后加入nohup:

$ nohup java -jar java-in-test.jar
nohup: 忽略输入并把输出追加到"nohup.out"

看起来好像没有问题。然而通过ps命令查看后,并没有这个进程,查看nohup.out时发现抛出了异常信息:

$ more nohup.out
Enter a Char:Exception in thread “main” java.io.IOException: 错误的文件描述符
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
at my.in.InTest.main(InTest.java:10)

实际情况与之前预想的并不一样。

nohup详解

在man中,nohup解释如下:

nohup - 使程序运行时不挂起, 不向 tty 输出信息

然而,在实际使用时,nohup其实做了三件事。阅读nohup源码可知:

ignoring_input = isatty (STDIN_FILENO);
redirecting_stdout = isatty (STDOUT_FILENO);
stdout_is_closed = (!redirecting_stdout && errno == EBADF);
redirecting_stderr = isatty (STDERR_FILENO);

首先,代码判断了标准输入、标准输出、标准错误输出是否为终端。如果标准输入是终端,则将其重定向为 /dev/null,并且文件打开方式置为只写,这样如果试图从中读取时会引发错误。这也就解释了之前我们执行nohup java -jar java-in-test.jar命令时抛出了异常信息。

  /* If standard input is a tty, replace it with /dev/null if possible.
     Note that it is deliberately opened for *writing*,
     to ensure any read evokes an error.  */
  if (ignoring_input)
    {
      if (fd_reopen (STDIN_FILENO, "/dev/null", O_WRONLY, 0) < 0)
        error (exit_internal_failure, errno,
               _("failed to render standard input unusable"));
      if (!redirecting_stdout && !redirecting_stderr)
        error (0, 0, _("ignoring input"));
    }

如果标准输出和标准错误输出是终端,则将其重定向到当前目录下的nohup.out文件,如果文件打开失败,则重定向到HOME目录下的nohup.out文件。标准错误输出重定向到同一个文件中。

  /* If standard output is a tty, redirect it (appending) to a file.
     First try nohup.out, then $HOME/nohup.out.  If standard error is
     a tty and standard output is closed, open nohup.out or
     $HOME/nohup.out without redirecting anything.  */
  if (redirecting_stdout || (redirecting_stderr && stdout_is_closed))
    {
      char *in_home = NULL;
      char const *file = "nohup.out";
      int flags = O_CREAT | O_WRONLY | O_APPEND;
      mode_t mode = S_IRUSR | S_IWUSR;
      mode_t umask_value = umask (~mode);
      out_fd = (redirecting_stdout
                ? fd_reopen (STDOUT_FILENO, file, flags, mode)
                : open (file, flags, mode));

      if (out_fd < 0)
        {
          int saved_errno = errno;
          char const *home = getenv ("HOME");
          if (home)
            {
              in_home = file_name_concat (home, file, NULL);
              out_fd = (redirecting_stdout
                        ? fd_reopen (STDOUT_FILENO, in_home, flags, mode)
                        : open (in_home, flags, mode));
            }
          if (out_fd < 0)
            {
              int saved_errno2 = errno;
              error (0, saved_errno, _("failed to open %s"), quoteaf (file));
              if (in_home)
                error (0, saved_errno2, _("failed to open %s"),
                       quoteaf (in_home));
              return exit_internal_failure;
            }
          file = in_home;
        }

      umask (umask_value);
      error (0, 0,
             _(ignoring_input
               ? N_("ignoring input and appending output to %s")
               : N_("appending output to %s")),
             quoteaf (file));
      free (in_home);
    }

  /* If standard error is a tty, redirect it.  */
  if (redirecting_stderr)
    {
      /* Save a copy of stderr before redirecting, so we can use the original
         if execve fails.  It's no big deal if this dup fails.  It might
         not change anything, and at worst, it'll lead to suppression of
         the post-failed-execve diagnostic.  */
      saved_stderr_fd = fcntl (STDERR_FILENO, F_DUPFD_CLOEXEC,
                               STDERR_FILENO + 1);

      if (!redirecting_stdout)
        error (0, 0,
               _(ignoring_input
                 ? N_("ignoring input and redirecting stderr to stdout")
                 : N_("redirecting stderr to stdout")));

      if (dup2 (out_fd, STDERR_FILENO) < 0)
        error (exit_internal_failure, errno,
               _("failed to redirect standard error"));

      if (stdout_is_closed)
        close (out_fd);
    }

同时,nohup命令会阻止SIGHUP信号。

signal (SIGHUP, SIG_IGN);

&详解

在命令最后添加&时,命令会转入后台运行。查看任务控制源码如下:

  /* If the last arg on the line is '&', then start this job in the
     background.  Else, fg the job. */
  for (t = list; t && t->next; t = t->next)
    ;
  fg_bit = (t && t->word->word[0] == '&' && t->word->word[1] == '\0') == 0;

  return (fg_bg (list, fg_bit));

转入后台的任务会阻断标准输入,如果程序试图读取,会被挂起。注意,后台任务的标准输出不会阻断。

$ java -jar java-in-test.jar &
[2] 3690
$ Enter a Char:
[2] + 3690 suspended (tty input) java -jar java-in-test.jar
$ jobs -l
[1] - 7610 suspended (tty output) man
[2] + 3690 suspended (tty input) java -jar java-in-test.jar

通过命令fg %job_number可将任务调到前台运行,在本例中,job_number是2。注意此处不要和进程号3690混淆。

$ fg %2

关闭当前shell,任务即被注销。

将nohup和&结合起来使用,可将任务转入后台永久运行,不受SIGHUP信号的影响,即关闭终端程序依然可以运行。但是在本例中,运行会抛出异常,因为nohup重定向了标准输入。如果有类似的需求,可以通过创建socket连接、显式重定向输入为文件等方式来实现。

$ nohup java -jar java-in-test.jar &
[1] 6308
nohup: 忽略输入并把输出追加到"nohup.out"
$
[1] + 6308 exit 1 nohup java -jar java-in-test.jar
$ more nohup.out
Enter a Char:Exception in thread “main” java.io.IOException: 错误的文件描述符
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
at my.in.InTest.main(InTest.java:11)

需注意,关闭终端后,任务无法再通过fg调出。

#!/bin/bash # 定义 JAR 文件路径 JAR_FILES=( &quot;bank-system.jar&quot; &quot;ruoyi-auth.jar&quot; &quot;ruoyi-gateway.jar&quot; &quot;ruoyi-modules-file.jar&quot; &quot;ruoyi-modules-gen.jar&quot; &quot;ruoyi-modules-job.jar&quot; &quot;ruoyi-modules-system.jar&quot; &quot;ruoyi-visual-monitor.jar&quot; ) # 指定最小和最大内存 MIN_MEMORY=&quot;64m&quot; MAX_MEMORY=&quot;128m&quot; # 指定激活的 YAML 配置文件 SPRING_PROFILE=&quot;production&quot; # 检查是否传入了要重启的 JAR 包名称 if [ $# -eq 1 ]; then TARGET_JAR=$1 if [[ &quot; ${JAR_FILES[@]} &quot; =~ &quot; ${TARGET_JAR} &quot; ]]; then RESTART_JARS=($TARGET_JAR) else echo &quot;指定的 JAR 包 $TARGET_JAR 不在列表中,请检查。&quot; exit 1 fi else RESTART_JARS=(&quot;${JAR_FILES[@]}&quot;) fi # 获取当前运行的 Java 进程列表 PIDS=$(ps -ef | grep java | grep -E &quot;$(IFS=&#39;|&#39;; echo &quot;${RESTART_JARS[*]%.*}&quot;)&quot; | awk &#39;{print $2}&#39;) # 关闭现有的服务 if [ -n &quot;$PIDS&quot; ]; then echo &quot;Stopping existing services...&quot; for PID in $PIDS; do kill -9 $PID echo &quot;Stopped service with PID: $PID&quot; done else echo &quot;未找到需要停止的服务&quot; fi # 等待几秒钟以确保所有服务都已完全停止 sleep 5 # 启动指定的服务 for JAR_FILE in &quot;${RESTART_JARS[@]}&quot;; do echo &quot;Starting service: $JAR_FILE&quot; nohup java -Xms$MIN_MEMORY -Xmx$MAX_MEMORY -Dspring.profiles.active=$SPRING_PROFILE -jar $JAR_FILE &gt; /dev/null 2&gt;&amp;1 &amp; echo &quot;Service $JAR_FILE started with PID $!&quot; done echo &quot;指定的服务已重启。&quot;
04-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值