Java 分析ntpq命令结果的空字符串

Java 分析ntpq命令结果的空字符串,java,unix,process,processbuilder,runtime.exec,Java,Unix,Process,Processbuilder,Runtime.exec,我正在分析执行此复合命令的结果 ntpq -c peers | awk ' $0 ~ /^*/ {print $9}' 以获取活动ntp服务器的偏移量 这是定期使用和执行的java代码 public Double getClockOffset() { Double localClockOffset = null; try { String[] cmd = {"/bin/sh", "-c",

我正在分析执行此复合命令的结果

  ntpq -c peers | awk ' $0 ~ /^*/ {print $9}'
以获取活动ntp服务器的偏移量

这是定期使用和执行的java代码

 public Double getClockOffset() {
    Double localClockOffset = null;

    try {

        String[] cmd = {"/bin/sh", 
                        "-c", 
                        "ntpq -c peers | awk \' $0 ~ /^\\*/ {print $9}\'"};

        Process p = Runtime.getRuntime().exec(cmd);

        p.waitFor();

        BufferedReader buf = new BufferedReader(new InputStreamReader(p.getInputStream()));

        String line = buf.readLine();

        if (!StringUtils.isEmpty(line)) {
            localClockOffset = Double.parseDouble(line.trim());
        } else {
            // Log "NTP -> Empty line - No active servers - Unsynchronized"
        }
    } catch (Exception e) {
        // Log exception
    }

    return localClockOffset;
}
ntpq结果示例

>      remote           refid      st t when poll reach   delay   offset  jitter
> ==============================================================================
> *server001s1     .LOCL.           1 u   33   64  377    0.111   -0.017   0.011
> +server002s1     10.30.10.6       2 u   42   64  377    0.106   -0.006   0.027
> +server003s1     10.30.10.6       2 u   13   64  377    0.120   -0.009   0.016
请注意,awk搜索以“*”开头的第一行并提取其第九列。在示例中:-0.017

问题是,有时在通过控制台执行命令时返回一个数字时,我会获取no active servers log消息(当没有带“*”的服务器时,会出现该消息)


我知道我没有关闭该代码中的
BufferedReader
,但这就是这种行为的原因吗?在每次方法调用中都会创建一个新实例(并保持打开状态直到垃圾回收),但我认为这不应该是这个问题的原因。

正如Andrew Thompson所指出的,您应该尝试
ProcessBuilder

String[] cmd = {"/bin/sh", 
                        "-c", 
                        "ntpq -c peers | awk \' $0 ~ /^\\*/ {print $9}\'"};
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);

Process proc = pb.start();
BufferedReader buf = new BufferedReader(new 
InputStreamReader(proc.getInputStream()));
String line = null;
while ((line = buf.readLine()) != null) {
   localClockOffset = Double.parseDouble(line.trim());
   break;
}

proc.destroy();
Ref

Runtime.exec()只需调用其中的ProcessBuilder,如下所示:

public Process More ...exec(String[] cmdarray, String[] envp, File dir)
    throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}

因此,使用它而不是按原样使用
ProcessBuilder
并没有什么错

问题是您调用了:

p.waitFor();
在获得
InputStream
之前

这意味着,当您获得
InputStream
时,进程将被终止,并且输出流数据可能对您可用,也可能不可用,这取决于操作系统缓冲实现的细微差别和操作的精确计时

因此,如果您将
waitFor()
移到底部,您的代码应该可以更可靠地开始工作

但是,在Linux下,您通常应该能够从管道缓冲区读取剩余数据,即使在写入过程结束之后也是如此

OpenJDK中的实现实际上明确地利用了这一点,并尝试在进程退出后排出剩余的数据,以便回收文件描述符:

/** Called by the process reaper thread when the process exits. */
synchronized void processExited() {
    synchronized (closeLock) {
        try {
            InputStream in = this.in;
            // this stream is closed if and only if: in == null
            if (in != null) {
                byte[] stragglers = drainInputStream(in);
                in.close();
                this.in = (stragglers == null) ?
                    ProcessBuilder.NullInputStream.INSTANCE :
                    new ByteArrayInputStream(stragglers);
            }
        } catch (IOException ignored) {}
    }
}
至少在我的测试中,这似乎工作得足够可靠,因此最好知道您运行的是哪个特定版本的Linux | Unix和JRE

您是否也考虑过应用程序级问题的可能性? 也就是说,
ntpq
并不能保证总是返回
*

因此,最好从管道中删除
awk
部分,看看是否始终有一些输出

另一件需要注意的事情是,如果shell管道中的一个步骤失败(例如,ntpq本身),您还将获得一个空输出,因此您还必须跟踪STDERR(例如,通过
ProcessBuilder将其与STDOUT合并)

旁注


在开始使用数据之前执行
waitFor
,在任何情况下都是一个坏主意,因为如果您的外部进程将产生足够的输出来填充管道缓冲区,它将挂起等待有人读取,这是永远不会发生的,因为您的Java进程将同时被锁定在
等待

最后我们找到了真正的问题

我不会改变被接受的anwser,我认为它也很有用,但也许有人可以从我们的经验中学习

我的java程序是用shell脚本启动的。手动执行脚本时,会找到并成功调用
ntpq
命令。当软件完全部署时,问题就会出现。在最终的环境中,我们有一个cron调度的demon,它使我们的程序保持活动状态,但是cron建立的
PATH
与我们的概要文件分配的
PATH
不同

cron使用的路径

.:/usr/bin:/bin
路径
我们为手动启动脚本而登录的路径:

/usr/sbin:/usr/bin:/bin:/sbin:/usr/lib:/usr/lib64:/local/users/nor:
/usr/local/bin:/usr/local/lib:.
通常
ntpq
处于

/usr/sbin/ntpq
在我们找到问题的关键之后,我搜索了StackOverflow并得到了这个相关的问题,在那里问题得到了更好的解释和解决


如果您完成读取,为什么不关闭缓冲区?需要考虑的一件事是:我认为在获取输入流并开始读取之前,不需要等待进程退出。@breezee我已经发布了我的原始代码。这是个失误。我已经关闭了缓冲区,但我不确定这是否是所描述的奇怪行为的原因。请参阅关于正确创建和处理流程的许多好提示。然后忽略它引用
exec
,并使用
ProcessBuilder
创建流程。感谢@andreThompsoni链接我已经在使用ProcessBuilder了。几天前我找到了这个答案,但我想知道为什么我最初的方法有时会失败。那里发生了什么?我所理解的是
p.waitFor()
只会导致当前线程等待,而在waitFor之后获取的流将取决于许多因素。如本例所述,若您将在一个单独的线程中开始阅读,并且只阅读stdout和stderr,那个么您也将获得可预测的结果。“许多因素”?有点不舒服,不是吗?另外,我刚刚完整地阅读了你的答案,为什么我需要一个循环?我只想读一行。我仍然不知道为什么上面的代码有时会起作用,但是非常感谢你的评论,谢谢你的回答。关于您关于ntpq不返回“*”的声明,我对该算法并不感到自豪,但我不能改变它。问题是ntpq正在返回它,程序显示“找不到”。我的代码运行在Red Hat 2.6.32-642.el6.x86_64和jre 1.8.0_112-b15中,但我想您已经回答了我的问题。:-)