/usr/xpg4/bin/grep -q [^0-9] 并不总是按预期工作

/usr/xpg4/bin/grep -q [^0-9] does not always work as expected

我有一个多年来一直在日常使用的 Unix ksh 脚本(由 crontab 在晚上启动)。最近,脚本中的一个函数出现了前所未有的异常行为。我尝试了各种方法找出原因,但都没有成功。

该函数验证一个输入字符串,该字符串应该是由 10 个数字字符组成的字符串。函数检查字符串长度是否为10,是否包含任何非数字字符:

#! /bin/ksh

# The function:
is_valid_id () {
   # Takes one argument, which is the ID being tested.
   if [[ $(print ${#1}) -ne 10 ]] || print "" | /usr/xpg4/bin/grep -q [^0-9] ; then
      return 1
   else
      return 0
   fi
}

cat $input_file | while read line ; do
   id=$(print $line | awk -F: '{print }')
   # Calling the function:
   is_valid_id $id
   stat=$?
   if [[ $stat -eq 1 ]] ; then
      print "The ID $id is invalid.  Request rejected.\n" >> $ERRLOG
      continue
   else
      ...
   fi
done

该函数的问题在于,每天晚上,在数十个或数百个请求中,它会发现多个请求中的 ID 无效。我目视检查了输入数据,发现所有“无效”ID 实际上都是 10 个数字字符的字符串。这个错误似乎是 运行dom,因为它只发生在一些请求中。但是,尽管被拒绝的请求不断返回,但日复一日被选为无效的 ID 始终是相同的。

我做了以下事情:

  1. Unix 机器已经 运行 将近一年了,因此可能需要更新。系统管理员应我的要求重新启动机器。但是重启后问题依旧
  2. 我手动运行在函数里测试一模一样的两个,在命令提示符下,晚上发现无效的id都有效
  3. 我知道相同的命令在手动调用或在脚本中可能会有不同的行为。要查看函数在脚本中的行为,上面的代码摘录是我 运行 重现问题的小脚本。事实上,一些(虽然不是全部)在夜间发现无效的 ID 也被小型故障排除脚本发现无效。
  4. 然后我将该故障排除脚本修改为 运行 两个测试一次一个,发现是 /usr/xpg4/bin/grep -q [^0-9] 测试错误地发现某些 ID 包含非数字字符(秒)。好吧,ID 都是数字字符,至少在视觉上是这样。
  5. 我检查了xpg4 grep命令文件(ls -l /usr/xpg4/bin/grep)是否有问题,看看它是否最近放在那里。但它的时间戳是 2005 年(这台机器 运行s Solaris 10)。
  6. 知道数据来自中央 ERP 系统,使用各种终端机从不同位置执行数据输入运行使用各种可能的操作系统,支持各种字符集和编码。 ERP 系统只是允许它们。但是来自其他编码的字符是否可以在视觉上显示为数字字符,但编码值并不像 /usr/xpg4/bin/grep 命令期望的那样在我们的 Unix 机器上?我尝试了 od(八进制转储)命令,但它对我帮助不大,因为我不熟悉它。也许我需要更多地了解 od 才能解决这个问题。

我的临时解决方法是省略 /usr/xpg4/bin/grep -q [^0-9] 测试。但问题并没有得到解决。接下来我可以尝试什么?

您的有效性测试函数恰好比应有的复杂。例如。为什么要使用 print 代替 ${#1} 的命令?为什么不直接用${#1}呢?接下来,派生 grep 来测试非数字是一个缓慢且昂贵的操作。这个等效函数怎么样,100% POSIX 并且非常快:

is_valid_id () {
   # Takes one argument, which is the ID being tested.

   if test ${#1} -ne 10; then
      return 1                  # ID length not exactly 10.
   fi
   case  in
     (*[!0-9]*) return 1;;      # ID contains a non-digit.
     (*)        return 0;;      # ID is exactly 10 digits.
   esac
}

或者更简单,如果您不介意重复自己的话:

is_valid_id () {
   # Takes one argument, which is the ID being tested.
   case  in
     ([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9])   # 10 digits.
       return 0;;
     (*)
       return 1;;
   esac
}

这也避免了未加引号的 grep 模式的使用,这种模式在存在单字符文件名时容易出错。这样效果更好吗?