Shell-Script

POSIX 和可移植性 |外殼腳本 |grep -s,grep -q

  • May 14, 2019

我全力支持 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,第 7 版 Unixgrep不符合POSIX,因為它缺少-q並且其-s選項的行為類似於 GNUgrep-q選項。USG 風格grep也沒有-q,但它的-s選項表現得像 GNU grep。可移植的 shell 腳本應該避免兩者-q-s並且應該將標準和錯誤輸出重定向到/dev/null


我是不是已經做得過火了,還是重定向到/dev/null唯一的可移植方式?


關心四十年前作業系統版本的可移植性!

Unix V7 於 70 年代後期發布。那是引入 Bourne shell 的版本。

但是,當時還沒有添加功能支持,read沒有-r,也沒有printf命令。不區分大小寫grepgrep -y. 當然$(...)不是伯恩。

從那時起,類 Unix 系統發生了相當大的發展並出現了分歧。在 90 年代初期,POSIX 確實試圖恢復一些統一。

然而,仍然有一些系統在其預設實現中沒有遵循 POSIX,只是將符合 POSIX 的實現添加為單獨的實用程序。

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

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

在 Solaris 上工作時,您通常希望放在/usr/xpg4/bin前面$PATH並使用not(儘管在 Solaris 11/usr/xpg4/bin/sh/bin/sh改變了,其中 /bin/sh 現在是 ksh93,因此平均而言比非常錯誤的基於 ksh88 更符合 POSIX /usr/xpg4/bin/sh)。

對您的程式碼的其他一些可移植性評論:

  • 的行為correct_answers=$*read -r取決於 的目前值$IFS。($*將位置參數與 的第一個字元連接$IFSread用於$IFS將輸入拆分為單詞),因此您需要將其設置為所需的值。

通過將位置參數加入標量字元串,這意味著如果其中任何一個包含分隔符,它將無法正常工作,除非您使用 NL 作為分隔符,因為無論如何read只會讀取一行,因此答案不能包含換行符。

  • 您在%b-formatted 參數中包含了可能不打算\x擴展序列的內容。
  • for single_correct_answer in $correct_answers使用拆分+全域。我不認為你想要這裡的 glob 部分,並且再次進行連接以稍後再次拆分它有點愚蠢(不可靠)。
  • grep -i -x "$user_answer"進行正則表達式模式匹配,而不是不區分大小寫的比較。此外,如果答案以 開頭,它將無法正常工作-grep然後將其作為一個選項。
  • printf '%s' text產生非文本輸出(缺少換行符),因此grepon 它的行為是未指定的(實際上是不可移植的)。

因此,考慮到這些,我會將程式碼更改為:

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
}

引用自:https://unix.stackexchange.com/questions/518795