我有一个 Wifi 天线,我想优化它的位置。对于这个建议,我想实时可视化当前的延迟。
我编写了一行脚本来输出 ping 延迟:
纬度:
ping 8.8.8.8|gawk '/64 bytes/{ match($7,/[0-9.]+/,arr); print((i++)," ",arr[0]);}'
我想用 gnuplot 绘制它:
plot "<lat.sh"
那是行不通的。我发现 lat.sh 在终端上显示它应该显示的内容,例如:
$ ./lat.sh
0 47.7
1 25.5
2 15.8
3 16.7
但是,将其输出到管道是行不通的。./lat.sh > outfile
或./lat.sh|tee outfile
或./lat.sh &> outfile
不向终端打印任何内容,但文件(或管道)保持为空。 gnuplot 管道也是如此。我真的很困惑。
答案1
作为麦克塞夫解释下面,这是一个缓冲问题。解决这个问题的一种方法是强制awk
立即使用fflush()
(其余的只是您正在做的事情的简化版本):
ping 8.8.8.8 | gawk -F'[= ]' '/^64/{print i++,$(NF-1); fflush()}'
该fflush()
调用将强制awk
在输出可用时立即打印输出,而无需等待其缓冲区填满。
或者,您可以尝试运行ping
特定次数。例如,10:
ping -c 10 8.8.8.8 | gawk ...
答案2
看来问题出在这里awk
。您的程序省略了输出 - 修剪它。这可能是一件非常有用的事情,但是当您这样做时,您会减少排队的输出awk
。大多数 UNIX 程序使用stdio.h (这是大多数 UNIX 程序)将根据输出设备的类型缓冲其输出。当输出设备是终端时,大多数 UNIX 程序都会对其输出进行行缓冲 - 并且write()
每行执行一次。几时不是尽管,(例如当它是管道时)所述程序将缓冲每次写入的一些定义的字节数。
这是标准行为,通常是好的事物。每次调用write()
都是系统调用 - 它是系统内核核心和最基本功能的挂钩。 UNIX 系统是分时的- 内核根据需要将 CPU 的处理时间分配给每个请求进程。所以它支付为了提高效率并将您的 I/O 分成可管理的块。这就是应用程序缓冲的原因。
这就是awk
我们正在做的事情。这个问题是混合在一起的,因为它对输入的过滤将其自身的输出减少到每秒大约 8 或 10 字节左右。如果awk
将输出缓冲成 4kb 块,(这是常见的),并且它每秒仅生成大约 10 个字节的输出,因此最终每 6 分钟只写入一次。除非您正在检查大陆漂移规模的某些频率,否则它不会成为测量延迟的非常有用的应用程序。
有一些方法可以处理这个问题。例如,您可以将管道包装在 pty 中 -screen
可以为您和其他几个应用程序执行此操作。可能有awk
-native 选项来处理这个问题 - 但我不知道(即使特登肯定会)。 GNU 的工具可以stdbuf
注入 libc 调用,以便在程序执行时设置输出缓冲区。这通常非常有效,但如果稍后执行的程序在执行过程中调整其缓冲区,则不会很有用。
现在我责备awk
但ping
也可能缓冲输出。然而 - 至少在我的 Linux 系统上 - 当我这样称呼它时:
ping -O -n 8.8.8.8 | ...
...不是这种情况。如果您好奇的话,我设法让它在每个输出行可用时一致地写入每个输出行,仅使用标准 UNIX 命令的非常基本的 POSIX 选项。
最简单:
ping -On 8.8.8.8 |
sed -u 's/^64.*=\(.*\) ttl.*=/\1 /' |
more pipeline
...将与 GNU、BSD 或 AST 一起使用sed
。另外,还有...
ping -On 8.8.8.8 |
sed 's/^64.*=\(.*\) ttl.*=/\1 /w target_file' |
more pipeline
...它将在任何可能的处理之后将所有输入行复制到标准输出 - 这很可能是块缓冲的。然而,它会还立即将每次成功替换w
的结果写入s///
目标文件。w
任何/所有指定仪式文件的立即仪式w
是 POSIX 规范的sed
行为 - 也是如此(几乎)对于r
ead 文件也是如此。作为该主题的细微变化:
ping -On 8.8.8.8 |
sed 's/^64.*=\(.*\) ttl.*=/\1 /w /dev/fd/1' |
more pipeline
在支持使用链接寻址文件描述符的系统上/dev
,上述内容应该会通过标准 shell 管道产生行缓冲输出 - 至少就目前而言sed
是这样。对于那些不直接支持/dev/fd
链接的系统,它仍然是一个流行的sed
处理功能/dev/std(in|out|err)
- 即使是旧系统也是minised
如此。用于-n
完全禁用默认的 stdout 输出。
这是基于该构造的更灵活且更复杂的解决方案:
mkfifo /tmp/ipipe /tmp/opipe
exec 9<>/tmp/opipe 8<>/tmp/ipipe
trap ' printf \\nTTY:STOP\\n >&8' INT
sed -n 's/^64.*=\(.*\) ttl.*=/\1\t/
/^TTY:START$/,/^TTY:STOP$/{
/^TTY/d;w /dev/tty
}; /^PIP:START$/,/^PIP:STOP$/{
/^PIP/d;w /tmp/opipe
}' <&8 >/dev/null 2>&1 & SEDPID=$!
ping -On 8.8.8.8 >&8 2>&8 & PNGPID=$!
printf \\nPIP:START\\n >&8;rm /tmp/?pipe
现在设置了一个后台调度程序进程,该进程将始终以行缓冲方式写入其输出。它将写入第一次调用时保存的文件描述符,该描述符对应于父 shell 的<>&9
描述符,或者写入 tty,具体取决于您是否向其发送命令:
PIP:START
TTY:START
您可以发送这些信息,例如:
printf \\nPIP:START\\n >&8
printf \\nTTY:START\\n >&8
它将根据您的需要同时处理两者。父 shell 已trap
配置为向其发送命令TTY:停止当它收到一个INT信号 - 所以只需按下CTRL+C您键盘上的内容应该足以使其在任何给定时间停止写入您的终端。不过,您需要明确告诉它停止写入管道,trap
如果您愿意,也可以在 a 中处理。你可以告诉它停止,如下所示:
printf \\nPIP:STOP\\n >&8
sed
将要总是 w
写入行缓冲区 - 无论它如何缓冲其标准输出。这样,您就可以在ping
写入后立即读取其写入的每一行。您可以通过调用另一个进程来读取文件描述符 9 来完成此操作 - 这是画中画 sed
当指示这样做时写入 - 或者您可以将其直接打印到终端。调用另一个进程来读取画中画做就是了:
cat <&9
...或类似的。另请注意,一旦可能需要的所有进程在这些管道上明确建立了其文件句柄,我就会明确为所使用的管道创建文件系统rm
链接。mkfifo
这意味着您将无法通过文件系统与涉及的进程进行交互 - 所有 IPC 必须通过父 shell 的描述符 8 和 9 进行协调。后台进程sed
和ping
进程的 PID 保存在 shell 变量$PNGPID
和中$SEDPID
。两者都可以通过 来解决kill
。
现在已经运行上面的脚本示例,然后发送电传打字机:开始命令结果是:
[mikeserv@localhost ~]$ printf \\nTTY:START\\n >&8
[mikeserv@localhost ~]$ 218 24.2 ms
219 21.2 ms
220 23.1 ms
221 21.3 ms
222 21.9 ms
^C
[mikeserv@localhost ~]$
...但是这两个进程仍在运行 -sed
只是没有在任何地方写入输出。
ps -Fp "$SEDPID" "$PNGPID"
UID PID PPID C SZ RSS PSR STIME TTY STAT TIME CMD
mikeserv 31601 28945 0 2143 1712 4 14:22 pts/0 SN 0:00 sed -n s/^64.*q=\(.*\) ttl.*=/\1\
mikeserv 31602 28945 0 2106 740 3 14:22 pts/0 SN 0:00 ping -On 8.8.8.8
答案3
Unix 在管道方面似乎确实很有限。看来我已经先将数据写入文件中,以便用它做任何有意义的事情。如果我重写整个过程,虚拟终端可能是解决方案。但我不会使用花哨的 GUI,而是简单地使用终端并使用 awk 渲染进度条。
ping -i 0.5 8.8.8.8 | gawk -F'[= ]' '/^64/{ for(i=0;i<($(NF-1)/3);i++) printf(" "); printf("|"); for(i=120;i>$(NF-1);i--) printf(" "); printf("\r");}'
将 120 替换为终端的宽度。