仅读取可用字符

时间:2014-10-01 07:56:29

标签: bash shell

好的,所以我有一个脚本正在侦听来自命令的一些输入,我希望能够操纵到目前为止到达的所有内容(如果有的话),但我开始怀疑这是否是均匀的可以使用read

这是我的意思的一个例子:

#!/bin/bash
long_running_task() {
    i=0
    while [ $i -lt 10 ]; do
        i=$(($i + 1))
        printf '%s' "Task $i "
        sleep 1
    done
}

read_output() {
    status=0
    while [ $status = 0 ]; do
        IFS='' read -rd '' -n 16 text; status=$?
        [ -z "$text" ] && continue

        printf '%s %s' "(${#text})" "$text"
    done
    echo
}

long_running_task | read_output

正如您所看到的,read_output功能只会在有16个字符可用时(或者到达输入结束时)打印文本,而不是打印可用的内容。

不幸的是我无法控制长时间运行的任务的输出,所以我不能简单地选择不同的分隔符。目前,我可以解决这个问题的唯一方法(我知道)是read一次只获取一个字符,但这非常低效。

有没有办法让read或其他东西获取尽可能多的输入然后返回它,但仍提供一种方法来检测输入的结束是否到达(而不是那里)只是暂时没有输出来捕捉?)

我目前正在工作的环境是bash所以我会接受任何特定于bash的解决方案,但如果有任何便携式选项,那么我也很乐意看到这些。命令返回的速度也无关紧要,只要它在合理的时间内完成(因此我可以每隔几秒查询一次输出)。

[编辑] 由于对这个问题存在一些困惑,我将尝试提供一个更具功能性的例子:

#!/bin/bash
long_running_task() {
    i=0
    while [ $i -lt 10 ]; do
        i=$(($i + 1))
        printf '%s' "Waiting $i second(s)… "
        sleep $i
        printf '%s\n' 'done.'
    done
}

NL=$'\n'
log() {
    status=0
    while [ $status = 0 ]; do
        IFS='' read -rd '' -n 16 text; status=$?
        [ -z "$text" ] && continue

        printf '%s' "${text/$NL/$NL[$(date +%R)] }"
    done
}

long_running_task | log

还是有点简单,但正如你所看到的那样,从长时间运行的任务中获取输出并且进程新行在需要它们的行的开头添加时间戳;它并不完美但它有希望提供基本的想法。这不是我真正想要做的,但我确实需要处理新行,不幸的是,这似乎是唯一的方法,但read迫使我等待它没有足够的响应。< / p>

2 个答案:

答案 0 :(得分:1)

@Haravikk:你的问题与我的问题密切相关 here on SO

不幸的是,许多实验和我从@chepner得到的唯一答案让我觉得没有简单的方法让read做他们想做的事。似乎read只能以三种方式吞下大量文本:

  • 获取换行符(或其他自定义分隔符,至少在zsh中)
  • 在预定数量的字符之后
  • 在预定义的超时后

PS:read不会采取迄今为止到达的任何内容是有道理的:假设您的read_output函数执行字符串替换,例如sed "s/Hello/World/g",并且您的任务产生Hel并且经过很长时间lo。以块的形式读取输出会错过替换,这可能与read_output的目的相反;捕获替换意味着在它们出现时采用块(基于一些基于时间或基于字符的算法),但也连接和解析以前的块,在这种情况下,您可能需要一个适当的解析器而不是普通的read ...

答案 1 :(得分:0)

不幸的是,我不得不接受妥协;虽然我想避免阅读单个字符,但它似乎是避免read突然阻止数据到达的唯一选择。

所以我的解决方案如下所示:

log() {
    status=0; new_line=1
    while [ $status = 0 ]; do
        IFS='' read -rn 1 char; status=$?
        if [ -z "$char" ]; then
             [ $status = 0 ] && { printf '\n'; new_line=1; }
             continue
        fi

        [ $new_line = 1 ] && { printf '%s' "[$(date +%R)] "; new_line=0; }
        printf '%s' "$char"
    done
}

这适用于纯shell版本,实际上应该适用于大多数shell环境(不仅仅是bash,除非我错过了什么)。当然要输出到stdout它很好,但是如果要将其调整为输出到文件那么它应该通过管道teeprintf语句需要指向一个文件描述符,否则它非常慢(因为它会为每个字符重新打开文件)。

当然其他语言如pythonperl可能是更好的解决方案,我会在可用的情况下使用它们,但这需要我的后备。我想我也会将其使用作为可选项,因此它仅用于期望从其运行的某些程序中获得部分输入的脚本。