可能的竞争条件,来自多个发球台接收者的管道输出在BASH脚本中的命名管道上无序到达

Dan*_*uus 5 bash pipe race-condition conditional-statements

更新:虽然没有真正解决我的管道工作中出现的原始问题,但我已经通过大大简化它解决了我的问题,并且完全抛弃了管道.这是一个概念验证脚本,可以在从磁盘,CRC32,MD5,SHA1,SHA224,SHA256,SHA384和SHA512校验和中读取一次并行生成,并将它们作为JSON对象返回(将在PHP中使用输出)和Ruby).这是原始的没有错误检查,但它的工作原理:

#!/bin/bash

checksums="`tee <"$1" \
        >( cfv -C -q -t sfv -f - - | tail -n 1 | sed -e 's/^.* \([a-fA-F0-9]\{8\}\)$/"crc32":"\1"/' ) \
        >( md5sum - | sed -e 's/^\([a-fA-F0-9]\{32\}\) .*$/"md5":"\1"/' ) \
        >( sha1sum - | sed -e 's/^\([a-fA-F0-9]\{40\}\) .*$/"sha1":"\1"/' ) \
        >( sha224sum - | sed -e 's/^\([a-fA-F0-9]\{56\}\) .*$/"sha224":"\1"/' ) \
        >( sha256sum - | sed -e 's/^\([a-fA-F0-9]\{64\}\) .*$/"sha256":"\1"/' ) \
        >( sha384sum - | sed -e 's/^\([a-fA-F0-9]\{96\}\) .*$/"sha384":"\1"/' ) \
        >( sha512sum - | sed -e 's/^\([a-fA-F0-9]\{128\}\) .*$/"sha512":"\1"/') \
        >/dev/null`\ 
"

json="{"

for checksum in $checksums; do json="$json$checksum,"; done

echo "${json:0: -1}}"
Run Code Online (Sandbox Code Playgroud)

原始问题:

我有点害怕提出这个问题,因为我在我的搜索短语中得到了如此多的点击,在应用了使用命名管道与bash收集的知识后- 数据丢失的问题,以及另外20页的阅读,我仍然在这有点停滞不前.

所以,为了继续,我正在做一个简单的脚本,使我能够同时在文件上创建CRC32,MD5和SHA1校验和,同时只从磁盘读取一次.我正在为此目的使用cfv.

最初,我只是简单地编写了一个简单的脚本,该脚本用三个cfv命令编写文件来编写te/tmp /下的三个单独文件,然后尝试将它们写入stdout,但最后输出空输出,除非在尝试读取文件之前,我让我的脚本睡了一秒钟.考虑到这很奇怪,我认为我的脚本中是个白痴,所以我尝试通过将cfv worker输出到命名管道来做一个不同的方法.到目前为止,这是我的脚本,在应用了上述链接的技术之后:

!/bin/bash

# Bail out if argument isn't a file:
[ ! -f "$1" ] && echo "'$1' is not a file!" && exit 1

# Choose a name for a pipe to stuff with CFV output:
pipe="/tmp/pipe.chksms"

# Don't leave an orphaned pipe on exiting or being terminated:
trap "rm -f $pipe; exit" EXIT TERM

# Create the pipe (except if it already exists (e.g. SIGKILL'ed b4)):
[ -p "$pipe" ] || mkfifo $pipe

# Start a background process that reads from the pipe and echoes what it
# receives to stdout (notice the pipe is attached last, at done):
while true; do
        while read line; do
                [ "$line" = "EOP" ] && echo "quitting now" && exit 0
                echo "$line"
        done
done <$pipe 3>$pipe & # This 3> business is to make sure there's always
                      # at least one producer attached to the pipe (the
                      # consumer loop itself) until we're done.

# This sort of works without "hacks", but tail errors out when the pipe is
# killed, naturally, and script seems to "hang" until I press enter after,
# which I believe is actually EOF to tail, so it's no solution anyway:
#tail -f $pipe &

tee <"$1" >( cfv -C -t sfv -f - - >$pipe ) >( cfv -C -t sha1 -f - - >$pipe ) >( cfv -C -t md5 -f - - >$pipe ) >/dev/null

#sleep 1s
echo "EOP" >$pipe
exit
Run Code Online (Sandbox Code Playgroud)

所以,按原样执行,我得到这个输出:

daniel@lnxsrv:~/tisso$ ./multisfv file
 :  :  : quitting now
- : Broken pipe (CF)
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
- : Broken pipe (CF)
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
- : Broken pipe (CF)
daniel@lnxsrv:~/tisso$ close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
Run Code Online (Sandbox Code Playgroud)

但是,随着睡眠1s的注释,我获得了预期的输出,

daniel@lnxsrv:~/tisso$ ./multisfv file
3bc1b5ff125e03fb35491e7d67014a3e *
-: 1 files, 1 OK.  0.013 seconds, 79311.7K/s
5e3bb0e3ec410a8d8e14fef1a6daababfc48c7ce *
-: 1 files, 1 OK.  0.016 seconds, 62455.0K/s
; Generated by cfv v1.18.3 on 2012-03-09 at 23:45.23
;
2a0feb38
-: 1 files, 1 OK.  0.051 seconds, 20012.9K/s
quitting now
Run Code Online (Sandbox Code Playgroud)

这让我很困惑,因为我假设tee不会退出,直到每个cfv接收者之后它才会退出数据,因此回声"EOP"语句将执行,直到所有cfv子流完成,这意味着他们会已将其输出写入我的命名管道......然后将执行echo语句.

由于行为是相同的没有管道,只是使用输出临时文件,我认为这必须是一些竞争条件与tee将数据推送到其收件人的方式?我尝试了一个简单的"等待"命令,但它当然会等待我的bash子进程 - while循环 - 完成,所以我只是暂停进程.

有任何想法吗?

TIA,丹尼尔:)

ric*_*ici 2

一旦 tee 将最后一位输入写入最后一个输出管道并关闭它(即 bash 创建的未命名管道,而不是 fifo,又名“命名管道”),tee 就会退出。无需等待读取管道的进程完成;事实上,它甚至不知道它正在写入管道。由于管道有缓冲区,因此 tee 很可能在另一端的进程完成读取之前完成写入。因此脚本会将“EOP”写入 fifo,导致读取循环终止。这将关闭 fifo 的唯一读取器,并且所有 cfv 进程在下次尝试写入 stdout 时都会收到 SIGPIPE。

这里要问的明显问题是为什么您不只运行三个(或 N 个)独立进程来读取文件并计算不同的摘要。如果“文件”实际上是动态生成的或从某个远程站点下载的,或者是其他一些缓慢的过程,则按照您尝试的方式执行操作可能是有意义的,但如果该文件存在于本地磁盘,很可能实际上只会发生一次磁盘访问;滞后的摘要器将从缓冲区高速缓存中读取文件。如果这就是您所需要的,GNU 并行应该可以正常工作,或者您可以在 bash 中启动进程(使用 &),然后等待它们。YMMV,但我认为这些解决方案中的任何一个都比设置所有这些管道并使用 tee 模拟用户空间中的缓冲区缓存占用的资源更少。

顺便说一下,如果你想序列化多个进程的输出,你可以使用flock实用程序。仅仅使用 fifo 是不够的;无法保证写入 fifo 的进程会自动写入整行,如果您知道它们这样做,则不需要 fifo。