【问题标题】:Hide cat prompt errors隐藏猫提示错误
【发布时间】:2016-02-07 11:03:31
【问题描述】:

我想设置一个脚本,以便不断解析 xml 文件中的特定标记。

脚本包含以下while循环:

function scan_t()
{
INPUT_FILE=${1}
while : ; do
   if [[ -f "$INPUT_FILE" ]]
   then
      ret=`cat ${INPUT_FILE} | grep "<data>" | awk -F"=|>" '{print $2}' | awk -F"=|<" '{print $1}'`
      if [[ "$ret" -ne 0 ]] && [[ -n "$ret" ]]
      then
         ...
      fi
   fi
done
} 
scant_t "/tmp/test.xml"

行格式为:

<data>0</data> or <data>1</data> <data>2</data> ..

即使条件 if [[ -f "$INPUT_FILE" ]] 已添加到脚本中,有时我也会得到:

cat: /tmp/test.xml: 没有这样的文件或目录。

确实,$INPUT_FILE 通常被另一个进程消耗,该进程负责在读取后抑制文件。

这个while 循环仅用于测试,cat 错误无关紧要,但我想隐藏这个返回,因为它对终端的污染很大。

【问题讨论】:

  • 这很奇怪,可能是竞争条件。要隐藏此类错误,您可以将stderr 重定向到/dev/null --> cat file 2&gt;/dev/null。另外,你说的不是cat file | grep | awk | awk。可能一个简单的awk '...' file 就足够了。
  • 感谢您的建议,我已经使用cat file | awk -F '[&lt;,&gt;]' '/data/ {printf "%s ",$3} END {print ""}'交换了
  • 不错!注意cat file | awk '...' 最好写成awk '...' file
  • 你试过像这样添加引号吗?:cat "${INPUT_FILE}"
  • 评论:要回复用户,请使用@ 符号(例如:@SnP)

标签: regex linux bash shell cat


【解决方案1】:

如果其他进程也可以在此脚本看到文件之前读取并删除该文件,则说明您的系统设计有竞争条件。 (我假设“负责压制”的意思是“旨在取消链接”......)

如果此脚本可以选择查看每个输入文件,则只需将 stderr 重定向到 /dev/null(即在竞争条件出现时忽略错误)。如果它不是可选的,则让这个脚本将输入文件重命名为其他名称,并让其他进程监视 that。在重命名之前检查该文件是否存在,以确保不会覆盖其他进程尚未读取的文件。


你的循环设计很糟糕。首先,您正忙于等待文件的生成(根本没有sleep)。其次,当输入存在时,您正在运行 4 个程序,而不是 1 个。

可以通过使用inotifywait 监视目录的更改来避免忙等待。因此if [[ -f $INPUT_FILE ]] 循环体仅在修改目录后运行,而不是 CPU 内核运行它的速度。

第二个更容易解决:永远不要cat file | somethingsomething filesomething &lt; file 如果 something 不在其命令行上使用文件名,或者行为不同。 cat 仅在您要连接多个文件时才有用。要将文件读入 shell 变量,请使用 foo=$(&lt;file)

我从 cmets 看到您已经设法将整个管道变成一个命令。所以写

INPUT_FILE=foo;
inotifywait -m -e close_write -e moved_to --format %f . |
while IFS= read -r event_file;do
    [[ $event_file == $INPUT_FILE ]] &&
       awk -F '[<,>]' '/data/ {printf "%s ",$3} END {print ""}' "$INPUT_FILE" 2>/dev/null

     #  echo "$event_file" &&
     #  date;
done
# tested and working with the commented-out echo/date commands

请注意,我正在等待close_write 和moved_to,而不是其他事件,以避免跳枪并读取未完成写入的文件。将$INPUT_FILE 放在它自己的目录中,这样您就不会因为其他文件名而唤醒您的循环。

要同时实现 rename-to-input-for-next-stage 建议,您可以在 awk 之后放置一个 while [[ -e $INPUT2 ]]; do sleep 0.2; done; mv -n "$INPUT_FILE" "$INPUT2"busy-wait 循环。


另一种方法是在每次循环迭代时运行一次inotifywait,但这有可能让您陷入在inotifywait 开始观看之前创建的$INPUT_FILE。因此生产者会等待消费者消费,而消费者不会看到该事件。

# Race condition with an asynchronous producer, DON'T USE
while inotifywait -qq -e close_write -e moved_to; do
    [[ $event_file == $INPUT_FILE ]] &&
       awk -F '[<,>]' '/data/ {printf "%s ",$3} END {print ""}' "$INPUT_FILE" 2>/dev/null
done

似乎没有办法指定尚不存在的文件的名称,即使作为过滤器也是如此,因此循环体需要在使用前测试目录中存在的特定文件。


如果您没有可用的 inotifywait,您可以将 sleep 放入循环中。 GNU sleep 支持小数秒,例如sleep 0.5。 Busybox 可能不会。无论如何,您可能想编写一个很小的琐碎 C 程序,它不断尝试在包含usleepnanosleep 的循环中open(2) 文件。当open 成功时,从那里重定向标准输入,然后exec 你的awk 程序。这样一来,statopen 之间就不会存在竞争。

#include <unistd.h>    // for usleep/dup2

#include <sys/types.h>  // for open
#include <sys/stat.h>
#include <fcntl.h>

#include <errno.h>
#include <stdio.h>  // for perror

void waitloop(const char *path)
{
    const char *const awk_args[] = { "-F", "[<,>]",
         "/data/ {printf \"%s \",$3} END {print \"\"}",
         path
    };
    while(42) {
        int fd = open(path, O_RDONLY);
        if (-1 != fd) {
            // if you fork() here, you can avoid the shell loop too.

            dup2(fd, 0);  // redirect stdin from fd.  In theory should check for error here, too.
            close(fd);   // and do this in the parent after fork
            execv("/usr/bin/awk", (char * const*)awk_args);  // execv's prototype doesn't prevent it from modifying the strings?
        } else if(errno != ENOENT) {
            perror("opening the file");
        } // else ignore ENOENT
        usleep(10000);  // 10 milliseconds.
    }

}
// optional TODO: error-check *all* the system calls.

这可以编译,但我还没有测试过。在单个进程中循环执行 open / usleep 比在 shell 中运行整个进程执行 sleep 0.01 轻得多。

更好的是使用 inotify 来监视目录事件以检测出现的文件,而不是 usleep。为避免争用,在设置 inotify watch 后,再次检查文件是否存在,以防它是在您上次检查之后但在 inotify watch 激活之前创建的。

【讨论】:

  • 重写显然是一个实质性的改进,但你可能注意到使用cat不仅仅是useless;如果您使用grep 直接读取文件,您可以简单地使用-s 隐藏错误消息。
  • 感谢彼得提供这些信息,这很有趣!但我应该提到我正在开发一个嵌入式平台,它的发行版是使用 Yocto 创建的。 inotify-tools 包目前不包含在文件系统中。因为包没有被定义为 poky 或 meta-oe 层,所以我无法添加它,即使我知道怎么做..
  • 另外你说"then have this script rename the input file to something else""Check for that file existing before you do the rename"。但是,如果在if [[ -f "$INPUT_FILE" ]] 之后执行mv,则提示错误将只是交换为mv : No Such File or Directory,不是吗?
  • @SnP:如果您有 $INPUT_FILE 的其他消费者等待不同的名称,它不会从该脚本窃取输入文件。不需要重定向mv 的stderr 以丢弃错误消息。
  • @SnP:为您的嵌入式平台定制构建 inotifywait,或使用来自 perl(或您已经在使用的其他语言)或定制 C 程序的 inotify 系统调用。无论如何,inotifywait 并没有为此用例提供理想的命令行界面。在单核嵌入式平台上,忙于等待文件显示是一个更糟糕的主意。即使浪费大部分 CPU 时间不是性能问题,它也会使您的设备运行得比其他情况下更热。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
相关资源
最近更新 更多