代码之家  ›  专栏  ›  技术社区  ›  pavanlimo

即使脚本执行完成,Java也会挂起。

  •  7
  • pavanlimo  · 技术社区  · 14 年前

    我试图从我的Java代码中执行脚本,看起来像:

    Process p = Runtime.getRuntime().exec(cmdarray, envp, dir); // cmdarray is a String array
    // consisting details of the script and its arguments
    
    final Thread err = new Thread(...); // Start reading error stream
    err.start();
    final Thread out = new Thread(...); // Start reading output stream
    out.start();
    p.waitFor();
    // Close resources 
    

    脚本的执行结束了(它不再是PID)了,但是Java被卡住了。 waitFor() 过程的方法!. 是的,我在两个单独的线程中读取输出和错误流。是的,它们在末端连接(之后 WAIT() )

    脚本基本上安装了一些RPM(比如10个左右)并对其进行配置。所以脚本运行60秒多一点。

    它看起来类似于以下内容:

    #!/bin/sh
    
    #exec 3>&1 >/var/log/some_log 2>&1
    
    # If the above line is uncommented, Java recognizes that the 
    # process is over and terminates fine.
    
    tar xzf a-package-having-rpms.tar.gz
    cd unpacked-folder
    (sh installer-script.sh) #This installs 10 odd rpms in a subshell and configures them
    cd ..
    rm -rf unpacked-folder
    
    exit 0
    

    令人震惊的是,如果我把下面的一行放在脚本(顶部),Java就知道脚本已经结束,它完美地终止了这个过程。

    exec 3>&1 > /var/log/some_log 2>&1
    

    对于记录,脚本不会生成任何输出。零字符!。因此,将exec语句置于此处毫无意义!

    但是,神奇的是,将脚本中的Excel语句放入Java中就可以了!. 为什么??

    如何避免脚本中的不合逻辑的exec语句?.

    如果您对installer-script.sh的外观感兴趣,那么:

    #!/bin/sh
    
    exec 3>&1 >>/var/log/another-log.log 2>&1
    INSDIR=$PWD
    RPMSDIR=$INSDIR/RPMS
    cd $RPMSDIR
    #
    rpm -i java-3.7.5-1.x86_64.rpm
    rpm -i --force perl-3.7.5-1.x86_64.rpm
    rpm -i --nodeps mysql-libs-5.0.51a-1.vs.i386.rpm
    rpm -i --nodeps mysql-5.0.51a-1.vs.i386.rpm
    rpm -i --nodeps mysql-server-5.0.51a-1.vs.i386.rpm
    rpm -i --nodeps perl-DBD-MySQL-3.0007-2.el5.i386.rpm
    rpm -i --nodeps perl-XML-Parser-2.34-6.1.2.2.1.i386.rpm
    .
    .
    .
    

    现在,为什么Java需要知道第一个脚本中的Excel命令已经结束了? 我怎样才能避开那个主管?,特别是因为第一个脚本不产生任何输出。

    屏住呼吸等待答案!

    7 回复  |  直到 14 年前
        1
  •  7
  •   Adrian Pronk    14 年前

    我的猜测是Java不认为脚本已经结束,直到通过STDIN/STDUD/STDRR传递给它的管道被子进程关闭。也就是说,在stdin上不再有活动的读卡器进程,在stdout/stderr上不再有活动的编写器进程。

    当你在管道上阅读时,你不会收到文件结束指示,除非 不再有进程 打开管道进行输出。因此,如果一个进程分叉,新进程继承了一个打开的文件句柄,那么原始进程终止,仍然有一个打开文件的进程,读卡器仍将等待。

    同样,对于正在编写的管道,在最后一个读卡器关闭管道之前,您不会收到“断管”信号。

    当脚本分叉继承stdin/stdout/stderr的后台任务(如新安装的服务)时,通常会出现此问题。

    通过使用exec,您将明确地破坏这些管道的继承链,以便后台进程不使用它们。

    如果在Linux上,为任何新服务检查/PRO/*/FD,看看它们的STDIN/STDUD/STDER是否是Java进程传递给脚本的同一个管道。

    同样的情况经常发生在运行/etc/init.d/xxx脚本时:当您从命令行运行它们时,它们会正常完成,但当您从某种监视器运行它们时,它们似乎会挂起。

    编辑:

    您说安装程序脚本包含以下行:

    exec 3>&1 >>/var/log/another-log.log 2>&1
    

    第一个术语3>&1将stdout克隆到文件描述符3(请参见 Redirections 在人巴什)。据我所知,fd 3没有特殊意义。然后,它通过打开/var/log/another-log.log来替换stdout,并通过克隆stdout来替换stderr。请参见bash手册页的重定向部分。

    这意味着新的文件描述符3已打开,可以在最初作为stdout传入的管道上进行写入。期望成为系统服务守护进程的程序通常会关闭文件描述符0(stdin)、1(stdout)和2(stderr),但不会影响任何其他程序。此外,现在shell已经打开了fd-3,它将把打开的文件传递给它执行的任何命令,包括后台命令。

    你知道安装程序打开fd 3有什么特别的原因吗?我的猜测是,如果您简单地从安装程序中删除“3>&1”术语,您的问题将得到解决。这将允许您从脚本中完全删除exec

        2
  •  1
  •   jilles    14 年前

    一个可能的原因是安装软件包会启动后台进程,使一个或两个管道(stdout/stderr)保持打开状态。因此,线程不会终止。这意味着这些后台进程没有正确地进行后台监控。正确的守护进程在完成初始化后将其原始stdin/stdout/stderr替换为/dev/null或日志文件。

        3
  •  0
  •   Zarkonnen    14 年前

    如果您在Linux/Unix上运行,请尝试 正在使用stdout/stderr。

    上次我不得不从Java运行外部脚本时,我不得不检查OS并只在Windows上附加输出线程线程。

        4
  •  0
  •   mdma    14 年前

    我也会泵的标准输入以及。泵送所有三种标准液流是个好主意。我无法解释,但我以前遇到过类似的问题,流程挂起,问题是缺少stdin泵。

        5
  •  0
  •   Adam Crume    14 年前

    我没有在Linux上尝试过,但在Windows中遇到了这个问题。我认为在流程终止之前,必须关闭流程中的标准。

        6
  •  0
  •   paulsm4    14 年前

    听起来你已经了解了问题的一些细节,但你还没有完全了解“大局”。

    1. 是的,从Java程序调用shell脚本是可能的。其实很简单! <=你已经知道了。

    2. 是的,一般来说,您应该能够看到任何打印到stderr或stdout的内容。

    3. 是的,您应该能够将stderr和/或stdout重定向到一个文件。

    4. 很明显,你的问题是,当你期望看到它时,你没有看到你期望看到的东西。这是正常的,这是预期的…而且,在更大的计划中,这是非常可取的。

    我认为,核心问题是“I/O缓冲”。

    缓冲是一件好事。

    我强烈建议您: 1。创建一个简单的单线程命令行Java测试程序,调用shell脚本。 <=您应该看到“预期结果”

    1. 用线程详细说明测试程序。 根据程序的结构,您可能会得到或可能不会得到“预期结果”。

    2. 将C程序(或其他Java程序)替换为shell脚本 <=这使得使用“stderr”(未缓冲)和“stdout”更容易。 你也应该失去“执行官”的胡言乱语-那是一个红鲱鱼!

    3. 实验,看看你学到了什么。

    祝你好运——而且请不要只是跳到第一个看似中肯的结论。总是检验你的理论。

    PS: 重复我的咒语:“I/O缓冲很好”;—)

    PPS:

    问:到目前为止,这个问题只被浏览了38次!

    A:“小蚱蜢,你必须学会耐心”;—)

    问:这对我来说有点关键!

    A:“你也必须学会谦虚”;—)

        7
  •  -1
  •   joe-mojo    14 年前

    你不能从Java执行脚本并等待它。必须执行该脚本的解释器。命令数组应该以“bash”开头,而不是以“myscript.bash”开头。