2

I am all in for portability in regards to shell scripts.

But I am unsure if I am overdoing it right now.

In this example, we have a function called confirmation, which accepts the very first argument as a string containing a question, and all of the other arguments are to be possible valid answers:


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

As you can see, the core part is utilizing grep, so I looked into the man page, and found this:

-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 .)

Let's stress this part:

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.


Am I overdoing it already, or is the redirection to /dev/null the only portable way?


I am not concerned about portability to an operating system version from forty years ago!

4

Unix V7 was released in the late 70s. That's the version that introduced the Bourne shell.

However, at that time, function support hadn't been added yet, read didn't have -r, there was no printf command. Case insensitive grep was with grep -y. And of course $(...) is not Bourne.

Unix-like systems have evolved quite a bit since then and diverged. In the early 90s POSIX did attempt to bring some unification back.

There are still however systems that haven't followed POSIX in their default implementations, only adding the POSIX-compliant implementations as separate utilities.

For instance, the /bin/grep of Solaris is closer to the V7 grep than to the POSIX grep. The POSIX grep on Solaris is in /usr/xpg4/bin/grep (not available in minimal deployments of Solaris).

/bin/grep on Solaris has no -q, -E, -F.

When working on Solaris, you generally want to put /usr/xpg4/bin at the front of $PATH and use /usr/xpg4/bin/sh instead of /bin/sh (though that changed in Solaris 11, where /bin/sh is now ksh93, so on average more POSIX compliant than the very buggy ksh88-based /usr/xpg4/bin/sh).

Some other portability comments on your code:

  • The behaviour of correct_answers=$* or read -r depends on the current value of $IFS. ($* joins the positional parameters with the first character of $IFS, read uses $IFS to split the input into words), so you'll want to set it to the value you want.

    By joining the positional parameters into a scalar string, that means it won't work properly if any of them contain the separator, unless you use NL as the separator as anyway read will only read one line so the answer cannot contain a newline.

  • you're including into the %b-formatted arguments things that are probably not meant to have \x sequences expanded.

  • for single_correct_answer in $correct_answers uses split+glob. I don't think you want the glob part here, and again it's a bit silly to do a join to later split it again (unreliably).

  • grep -i -x "$user_answer" does a regex pattern matching as opposed to a case insensitive comparison. Also, it won't work properly if the answer starts with -, as grep would then take it as an option.

  • printf '%s' text produces non-text output (missing newline), so the behaviour of grep on it is unspecified (and in practice non-portable).

So, with those in mind, I would change the code to:

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
}
| improve this answer | |
  • The ksh88 based ksh in /usr/xpg4/bin is closer to the POSIX standard than ksh93. Shells in general have various problems and none of the available shells is good enough to be named as the POSIX shell of the year. – schily May 20 '19 at 14:16
  • @schily, /usr/xpg4/bin/sh has countless bugs and deviations from the standard including major ones, all-in-all probably fewer than ksh93, see for instance the (long) discussion at mail-archive.com/austin-group-l@opengroup.org/msg01603.html (to which you took part). – Stéphane Chazelas May 20 '19 at 15:07
  • 1
    Well, I would be happy if there was one shell that is completely compliant but this is not available yet. The sad news for ksh93 is that there is no acceptable new maintainer since David did stop working on it. – schily May 21 '19 at 16:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.