我全力支持 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
选项的行为类似于 GNUgrep
。可移植 shell 脚本应该避免使用-q
and ,-s
并且应该将标准输出和错误输出重定向到/dev/null
。
我是否已经做得太过分了,或者重定向到/dev/null
唯一的便携式方式?
我不是关注四十年前操作系统版本的可移植性!
答案1
Unix V7 于 70 年代末发布。这是引入 Bourne shell 的版本。
不过当时还没有添加功能支持,read
没有-r
,也没有printf
命令。不区分大小写grep
的是grep -y
.当然$(...)
不是伯恩。
从那时起,类 Unix 系统已经发展了很多并且出现了分歧。在 90 年代初,POSIX 确实尝试恢复一些统一。
然而,仍然有一些系统在其默认实现中未遵循 POSIX,仅将符合 POSIX 的实现添加为单独的实用程序。
例如,/bin/grep
Solaris 的 更接近 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
。 ($*
将位置参数与 的第一个字符连接起来$IFS
,read
用于$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
}