POSIX 和可移植性 | shell 脚本 | grep -s、grep -q

POSIX 和可移植性 | shell 脚本 | grep -s、grep -q

我全力支持 shell 脚本的可移植性。

但我不确定我现在是否做得太过分了。

在此示例中,我们有一个名为 的函数confirmation,它接受第一个参数作为包含问题的字符串,并且所有其他参数都是可能的有效答案:


confirmation ()
{
    question=$1; shift; correct_answers=$*

    printf '%b' "$question\\nPlease answer [ $( printf '%s' "$correct_answers" | tr ' ' / ) ] to confirm (Not <Enter>): "; read -r user_answer

    # this part iterates through the list of correct answers
    # and compares each as the whole word (actually as the whole line) with the user answer
    for single_correct_answer in $correct_answers; do
        printf '%s' "$single_correct_answer" | grep -i -x "$user_answer" > /dev/null 2>&1 && return 0
    done

    return 1
}

confirmation 'Do you hate me?' yes yeah kinda

正如你所看到的,核心部分是利用grep,所以我研究了手册页,并发现了这个:

-q, --quiet, --silent
Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected. Also see the -s or --no-messages option. (-q is specified by POSIX .)
-s, --no-messages
Suppress error messages about nonexistent or unreadable files. Portability note: unlike GNU grep, 7th Edition Unix grep did not conform to POSIX , because it lacked -q and its -s option behaved like GNU grep's -q option. USG -style grep also lacked -q but its -s option behaved like GNU grep. Portable shell scripts should avoid both -q and -s and should redirect standard and error output to /dev/null instead. (-s is specified by POSIX .)

我们来强调一下这一部分:

可移植性说明:与 GNU 不同grep,第七版 Unixgrep不符合POSIX,因为它缺乏-q并且其-s选项的行为类似于 GNUgrep-q选项。 USG 风格grep也缺乏,-q但其-s选项的行为类似于 GNU grep。可移植 shell 脚本应该避免使用-qand ,-s并且应该将标准输出和错误输出重定向到/dev/null


我是否已经做得太过分了,或者重定向到/dev/null唯一的便携式方式?


不是关注四十年前操作系统版本的可移植性!

答案1

Unix V7 于 70 年代末发布。这是引入 Bourne shell 的版本。

不过当时还没有添加功能支持,read没有-r,也没有printf命令。不区分大小写grep的是grep -y.当然$(...)不是伯恩。

从那时起,类 Unix 系统已经发展了很多并且出现了分歧。在 90 年代初,POSIX 确实尝试恢复一些统一。

然而,仍然有一些系统在其默认实现中未遵循 POSIX,仅将符合 POSIX 的实现添加为单独的实用程序。

例如,/bin/grepSolaris 的 更接近 V7,grep而不是 POSIX grep。 Solaris 上的POSIXgrep已推出/usr/xpg4/bin/grep(在 Solaris 的最小部署中不可用)。

/bin/grep在 Solaris 上没有-q, -E, -F

在 Solaris 上工作时,您通常希望将 放在/usr/xpg4/bin前面$PATH并使用/usr/xpg4/bin/sh代替/bin/sh(尽管在 Solaris 11 中发生了变化,其中 /bin/sh 现在是 ksh93,因此平均而言比基于 bug 的 ksh88 更符合 POSIX 标准/usr/xpg4/bin/sh)。

关于您的代码的其他一些可移植性注释:

  • correct_answers=$*或 的行为read -r取决于 的当前值$IFS。 ($*将位置参数与 的第一个字符连接起来$IFSread用于$IFS将输入拆分为单词),因此您需要将其设置为您想要的值。

    通过将位置参数连接到标量字符串中,这意味着如果它们中的任何一个包含分隔符,它将无法正常工作,除非您使用 NL 作为分隔符,因为无论如何read只会读取一行,因此答案不能包含换行符。

  • 您在 -formatted 参数中包含了%b可能不意味着\x扩展序列的内容。

  • for single_correct_answer in $correct_answers使用 split+glob。我认为你不想要这里的全局部分,并且再次进行连接以稍后再次拆分它有点愚蠢(不可靠)。

  • grep -i -x "$user_answer"进行正则表达式模式匹配而不是不区分大小写的比较。另外,如果答案以 开头-,它将无法正常工作,因为grep它将作为一个选项。

  • printf '%s' text产生非文本输出(缺少换行符),因此其行为grep是未指定的(并且实际上是不可移植的)。

因此,考虑到这些,我会将代码更改为:

confirmation()  {
    question=$1; shift

    printf '%s\nPlease answer [' "$question"
    sep=
    for answer do
      printf %s "$sep$answer"
      sep=/
    done
    printf '] to confirm (Not <Enter>): '
    IFS= read -r user_answer

    # this part iterates through the list of correct answers
    # and compares each as the whole word (actually as the whole line)
    # with the user answer
    for single_correct_answer do
      printf '%s\n' "$single_correct_answer" |
        grep -ixFqe "$user_answer" && return
    done

    return 1
}

相关内容