如何在Shell脚本中处理NFS延迟问题

6

我正在编写一些 shell 脚本,其中经常会将一些内容写入文件,然后执行一个读取该文件的应用程序。我发现在我们公司中,网络延迟差异很大,所以简单的 sleep 2 等待时间可能不够稳健。

我尝试编写了一个(可配置的)超时循环,如下:

waitLoop()
{
   local timeout=$1
   local test="$2"

   if ! $test
   then
      local counter=0
      while ! $test && [ $counter -lt $timeout ]
      do
         sleep 1
         ((counter++))
      done

      if ! $test
      then
         exit 1
      fi
   fi
}

这对于test="[ -e $somefilename ]"有效。但是,仅测试文件是否存在是不够的,有时我需要测试某个字符串是否写入了该文件。我尝试过test="grep -sq \"^sometext$\" $somefilename",但这没有起作用。有人可以告诉我原因吗?
还有其他更简洁的选项来执行这样的测试吗?

能否简单地在文件上运行tail,并仅检查tail的输出(即最后几行)?这假设tail比您的脚本更聪明地检查文件何时更改(很可能,tail以更适当的方式执行此操作)。 - nos
我认为正确的方法取决于“一些东西”的内容以及“应用程序”的性质。您能否提供有关您要解决的实际问题的更多详细信息?所提出的问题有点模糊;可能存在您尚未考虑的完全不同的方法。 - Zac Thompson
8个回答

1

你可以这样设置你的测试变量:

test=$(grep -sq "^sometext$" $somefilename)

你的 grep 无法工作的原因是引号在参数中很难传递。你需要使用 eval

if ! eval $test

0

如果你想在“if”语句中使用waitLoop,你可能需要将“exit”改为“return”,这样脚本的其余部分就可以处理错误情况(否则脚本死亡之前甚至没有向用户显示失败的消息)。

另一个问题是使用“$test”来保存命令意味着实际执行时不会进行shell扩展,只会进行评估。因此,如果你说test="grep \"foo\" \"bar baz\"",而不是在名为“bar baz”的七个字符的文件中查找三个字母的字符串foo,它将在九个字符的文件“bar baz”中查找五个字符的字符串“foo”。

所以你可以决定不需要shell魔法,并设置test='grep -sq ^sometext$ somefilename',或者你可以使用类似以下内容的东西明确地处理引用:

if /bin/sh -c "$test"
then
   ...

0
尝试使用文件修改时间来检测它是否被写入,而无需打开它。类似这样的代码:
old_mtime=`stat --format="%Z" file`
# Write to file.
new_mtime=$old_mtime
while [[ "$old_mtime" -eq "$new_mtime" ]]; do 
  sleep 2;
  new_mtime=`stat --format="%Z" file`
done

如果有多个进程同时尝试访问文件,则这种方法将无法使用。


0

Shell 会将您的谓词拆分成单词。请使用下面代码中的 $@ 将其全部获取:

#! /bin/bash

waitFor()
{
  local tries=$1
  shift
  local predicate="$@"

  while [ $tries -ge 1 ]; do
    (( tries-- ))

    if $predicate >/dev/null 2>&1; then
      return
    else
      [ $tries -gt 0 ] && sleep 1
    fi
  done

  exit 1
}

pred='[ -e /etc/passwd ]'
waitFor 5 $pred
echo "$pred satisfied"

rm -f /tmp/baz
(sleep 2; echo blahblah >>/tmp/baz) &
(sleep 4; echo hasfoo   >>/tmp/baz) &

pred='grep ^hasfoo /tmp/baz'
waitFor 5 $pred
echo "$pred satisfied"

输出:
$ ./waitngo 
[ -e /etc/passwd ] 满足
grep ^hasfoo /tmp/baz 满足

太糟糕了,录制脚本并没有实时观看有趣。


0

我刚遇到了完全相同的问题。我使用了与您在OP中包含的超时等待类似的方法; 但是,我还包括了文件大小检查。如果自上次检查以来文件大小已增加,则会重置超时计时器。我正在编写的文件可能有几个Gig,因此需要一段时间才能通过NFS进行编写。

这对于您特定的情况可能有些过度,但我还让我的写入进程在完成写入后计算文件的哈希值。我使用的是md5,但crc32之类的东西也可以。这个哈希值从写入者广播到(多个)读者,读者会等待直到a)文件大小停止增加并且b)文件的(新计算的)哈希值与写入者发送的哈希值匹配。


谢谢。如何广播MD5哈希?那个哈希的等待循环本身就足够了吗? - andreas buykx
我们使用现成的网络消息传输协议(对于Perl来说是POE; 但是对于此功能,JMS或AMQP是相同的)。在我们的情况下,写入者和读取者位于不同的机器上,并且写入者在消息中广播哈希表示写入完成。读取者接收消息后,等待文件的大小停止增长,然后检查哈希值。 - Andrew Barnett
是的,在哈希上等待循环也可以工作,但计算哈希相对昂贵,而检查文件大小则不然,因此我做了一个简单/便宜的"门卫"来执行重/昂贵的操作。 - Andrew Barnett

0

我们有一个类似的问题,但原因不同。我们正在读取一个发送到SFTP服务器的文件,运行脚本的机器不是SFTP服务器。

我所做的是在cron中设置它(虽然循环加睡眠也可以),对文件进行cksum。当旧的cksum与当前的cksum匹配时(文件在确定的时间内未更改),我们知道写入已完成,并传输文件。

为了更加安全,我们在备份之前从不覆盖本地文件,并且仅在远程文件具有两个连续匹配的cksum并且该cksum与本地文件不匹配时才进行传输。

如果您需要代码示例,我肯定可以找到它们。


0

我会说,在文本文件中检查字符串的方法是使用grep。

你具体遇到了什么问题?

此外,您可能需要调整NFS挂载参数以消除根本问题。同步也可能有所帮助。请参阅NFS文档。


我没有更改挂载参数的可能性。我的脚本应该足够健壮,以处理延迟。问题不在于grep本身,而是grep测试未按照我的预期进行评估。 - andreas buykx
好的,如果你不能改变你的环境,那就是一个问题。无论如何,在修改文件后尝试过“同步”了吗?根据NFS文档,它应该可以做到(只要调用sysnc.2)。 - TheBonsai

-1

好的...这有点古怪...

如果您对文件有控制权:您可能能够在此处创建一个“命名管道”。 因此(取决于编写程序的方式),您可以以同步方式监视文件。

最简单的方法是:

创建命名管道:

mkfifo file.txt

设置同步接收器:

while :
do
    process.sh < file.txt
end

创建一个测试发送器:
echo "Hello There" > file.txt

'process.sh' 是你的逻辑所在的地方:这将阻塞直到发送者写入其输出。理论上,编写程序不需要修改....

警告:如果由于某种原因接收器未运行,可能会导致发送者被阻塞!

不确定它是否符合你的要求,但值得一看。

或者为了避免同步问题,可以尝试使用 'lsof' ?

http://en.wikipedia.org/wiki/Lsof

假设您只想在没有其他进程正在写入文件时从文件中读取(即,写入过程已完成)- 您可以检查是否有其他进程持有该文件句柄?

不错的想法,但需要注意的一点是,即使发送方和接收方具有相同的NFS挂载点,如果它们位于两个不同的主机上,命名管道也无法工作。 - dogbane
糟糕!应该仔细阅读标题 - 是的,在NFS挂载之间不起作用! - monojohnny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接