空字符串解析ntpq命令结果

Empty string parsing ntpq command result

我正在解析执行这个复合命令的结果

  ntpq -c peers | awk ' [=10=] ~ /^*/ {print }'

为了获取活动ntp服务器的偏移量。

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

 public Double getClockOffset() {
    Double localClockOffset = null;

    try {

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

        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 日志消息 - 旨在在没有带“*”的服务器时出现 - returns一个数字。

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

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

String[] cmd = {"/bin/sh", 
                        "-c", 
                        "ntpq -c peers | awk \' [=10=] ~ /^\*/ {print }\'"};
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();

参考 ProcessBuilder

Runtime.exec() 只是调用其中的 ProcessBuilder,就像这样:

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

OpenJDK Runtime.java

所以使用它代替 ProcessBuilder 没有任何问题。

问题是你调用了:

p.waitFor();

在您获得InputStream之前。

这意味着当您获得 InputStream 时,该进程已经终止,并且输出流数据可能对您可用,也可能不可用,具体取决于 OS缓冲实施的细微差别和精确的操作时间。

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

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

而 OpenJDK 中的 UNIXProcess 实现实际上明确地使用了它,并在进程退出后尝试耗尽剩余数据,以便可以回收文件描述符:

/** 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 的具体版本 运行.

您是否也考虑过应用程序级问题的可能性? IE。 ntpq 并不能真正保证总是 return 一个 * 行。

所以,最好从管道中删除 awk 部分,看看是否始终会有一些输出。

另一件需要注意的事情是,如果您的 shell 管道步骤之一失败(例如 ntpq 本身),您也会得到一个空输出,因此您还必须跟踪 STDERR(例如通过 ProcessBuilder).

将其与 STDOUT 合并

旁注

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

终于找到问题所在了。

我不会更改已接受的答案,我认为它也很有用,但也许有人可以从我们的经验中学习。

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

PATH 被 cron 使用:

.:/usr/bin:/bin

PATH 我们已登录以手动启动脚本:

/usr/sbin:/usr/bin:/bin:/sbin:/usr/lib:/usr/lib64:/local/users/nor:
/usr/local/bin:/usr/local/lib:.

通常 ntpq

/usr/sbin/ntpq

找到问题的关键后,我搜索 Whosebug 并得到了这个相关问题,其中问题得到了更好的解释和解决。

How to get CRON to call in the correct PATHs