Skip to content

Instantly share code, notes, and snippets.

@juliyvchirkov
Last active July 12, 2023 01:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juliyvchirkov/8f07b174a843f6b1a4dd212a42516f3d to your computer and use it in GitHub Desktop.
Save juliyvchirkov/8f07b174a843f6b1a4dd212a42516f3d to your computer and use it in GitHub Desktop.
sh: extended implementation of ”YesNoPrompt” shell function, fully POSIX compliant

YesNoPrompt

Extended implementation of ”YesNoPrompt” shell function, fully POSIX compliant

Designed, developed and tested in 2023 by Juliy V. Chirkov juliyvchirkov@gmail.com under the MIT license.

##################################
# shellcheck shell=sh enable=all #
##################################

yesnoprompt() {
    [ $# -eq 0 ] || printf -- "%s " "${*}" >&2
    printf "(Y/N)?%s" " " >&2

    if [ -t 0 ]; then
        stty -echo -icanon -isig

        while true; do
            REPLY="$(dd bs=1 count=1 2>/dev/null)"

            expr "${REPLY}" : "[^yYnN]" >/dev/null 2>&1 || break
        done

        stty echo icanon isig
    fi

    expr "${REPLY}" : "[yY]" >/dev/null 2>&1 && REPLY=Y || REPLY=N

    printf "%s\n" "${REPLY}" >&2

    [ "${REPLY}" = Y ]
}

Preamble

The above is the extended implementation of ”YesNoPrompt” shell function made fully POSIX compliant with the triple goal.

  1. To cover the most common shell interpreters in their ”minimal” (”strict”) mode, when these shells meet the POSIX specification like sh, the standard command language interpreter.
  2. To provide a decent reasonable alternative to the most widespread for years ”YesNoPrompt” implementation because of so called ”bashism”. The thing is the most common solution one can google thru the Internets is based on bash builtin command read, which is kinda superset over the classic read. Long story short, read -srn1, the key of ”YesNoPrompt” implementation in bash, is totally not portable, ʼcause neither -s nor -n option exists in the classic.
  3. To preserve nice look and feel of the UI produced by read -srn1 in bash with no use of read indeed because of the above on one hand and to implement the function at once as much ubique as possible and flexible to make ”YesNoPrompt” alternative really decent.

The coverage

Tested and confirmed to run the same cool way with the next shell interpreters: ash, bash, dash, ksh, mksh, posh, rbash, rksh, yash, zsh and busybox sh.

Dependencies

Depends on external base system binaries printf, stty, dd and expr (this oldschool quad should definitely be available anywhere out of the box).

Usage

Can be used along with a free style prompt like

yesnoprompt "Lorem ipsum dolor sit amet"

as well as just like yesnoprompt.

(Y/N)?” is appended in the first case or displayed alone in the second one automatically.

^C ^D and other control sequences as well as any input symbols except yYnN are silently ignored while the function waits for a choice.

When one of yYnN keys is pressed, the function displays the choice and quits with the standard return code - 0 for Y and 1 for N.

If the standard input stdin is not attached to a terminal (i.e. a script with ”YesNoPrompt” is running in non-interactive mode), the function does not wait for the input and quits immediately with N choice and return code 1 by default.

However, one can alter this behaviour by prepending REPLY variable with the value of lower y or upper Y to the command like

REPLY=Y yesnoprompt "Lorem ipsum dolor sit amet"

The default routine in non-ineractive mode will stay the same, the function wonʼt wait for the input and will immediately quit, but this time the choice will turn to Y and one will get 0 on return.

Bugs and features

If you are facing some bug or want to make a feature request, please leave a comment under this gist or drop a line to my e-mail, and thank you for your time and contribution in advance.

Support the creator

If you like the above, you can follow the links below to support the creator

Thanks from my heart to everyone who supports ✊🏻

Glory to Ukraine!

Juliy V. Chirkov juliyvchirkov

MIT License
Copyright (c) 2023 Juliy V. Chirkov <juliyvchirkov@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
##################################
# shellcheck shell=sh enable=all #
##################################
# Extended implementation of ”YesNoPrompt” shell function
# made fully POSIX compliant with the triple goal
#
# 1) To cover the most common shell interpreters in their
# ”minimal” (”strict”) mode, when these shells meet
# the POSIX specification like sh, the standard command
# language interpreter
#
# 2) To provide a decent reasonable alternative to the most
# widespread for years ”YesNoPrompt” implementation
# because of so called ”bashism”. The thing is the most
# common solution one can google thru the Internets is
# based on bash builtin command ”read”, which is kinda
# superset over the classic ”read”. Long story short,
# `read -srn1`, the key of ”YesNoPrompt” implementation
# in bash, is totally not portable, ʼcause neither -s
# nor -n option exists in the classic
#
# 3) To preserve nice look and feel of the UI produced by
# `read -srn1` in bash with no use of ”read” indeed
# because of the above on one hand and to implement
# the function at once as much ubique as possible
# and flexible to make ”YesNoPrompt” alternative
# really decent
#
# Depends on external base system binaries printff, stty,
# dd and expr (this oldschool quad should definitely be
# available anywhere out of the box)
#
# Tested and confirmed to run the same cool way with
# the next shell interpreters: ash, bash, dash, ksh,
# mksh, posh, rbash, rksh, yash, zsh and busybox sh
#
# Can be used along with a free style prompt like
# `yesnoprompt "Lorem ipsum dolor sit amet"` as well
# as just like `yesnoprompt`. ”(Y/N)?” is appended
# in first case or displayed alone in second one
# automatically
#
# ^C ^D and other control sequences as well as any
# input symbols except [yYnN] are silently ignored
# while the function waits for a choice
#
# When one of [yYnN] keys is pressed, the function
# displays the choice and quits with the standard
# return code - 0 for Y and 1 for N
#
# If the standard input stdin is not attached to
# a terminal (i.e. a script with ”YesNoPrompt” is
# running in non-interactive mode), the function
# does not wait for the input and quits immediately
# with N choice and return code 1 by default
#
# However, one can alter this behaviour by
# prepending REPLY variable with the value of
# lower ”y” or upper ”Y” to the command like
# `REPLY=Y yesnoprompt "Lorem ipsum dolor"`
#
# The default routine in non-ineractive mode
# will stay the same, the function wonʼt wait
# for the input and will immediately quit, but
# this time the choice will turn to Y and one
# will get 0 on return
#
# Designed, developed and tested in 2023 by Juliy V. Chirkov
# <juliyvchirkov@gmail.com> https://juliyvchirkov.github.io/
# under the MIT license https://juliyvchirkov.mit-license.org/
#
# Glory to Ukraine! 🇺🇦
#
# Juliy V. Chirkov, https://t.me/juliyvchirkov
yesnoprompt() {
[ $# -eq 0 ] || printf -- "%s " "${*}" >&2
printf "(Y/N)?%s" " " >&2
if [ -t 0 ]; then
stty -echo -icanon -isig
while true; do
REPLY="$(dd bs=1 count=1 2>/dev/null)"
expr "${REPLY}" : "[^yYnN]" >/dev/null 2>&1 || break
done
stty echo icanon isig
fi
expr "${REPLY}" : "[yY]" >/dev/null 2>&1 && REPLY=Y || REPLY=N
printf "%s\n" "${REPLY}" >&2
[ "${REPLY}" = Y ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment