Andrew Ferrier
Andrew Ferrier

Reputation: 17742

How can I prompt for yes/no style confirmation in a zsh script?

Using zsh, I'm trying to put a step in my ~/.zprofile where I interactively ask a yes/no style question. At first I tried this bash-style approach, but I saw errors of this form:

read: -p: no coprocess

(I'm aware that typically the zsh syntax is different from bash's - I tried preceding it with a sh emulation command - emulate -LR sh - but it made no difference).

This page implied the syntax might be different, so guided by this page and the zsh man page, I tried this instead:

read -q REPLY?"This is the question I want to ask?"

This instead fails with an error of the form:

/home/user/.zprofile:5: no matches found: REPLY?"This is the question I want to ask?"

How can I ask a simple yes/no question with zsh? Ideally the command would just swallow one character, with no need to press Enter/Return, and be 'safe' - i.e. the subsequent test defaults to no/false unless 'Y' or 'y' are entered.

Upvotes: 50

Views: 30568

Answers (4)

Dean Rather
Dean Rather

Reputation: 32394

I created two utility scripts for this:

  • read-string.sh reads a string from input (requires user to press enter)
  • read-char.sh reads a single char from input (does not require pressing enter)

Both scripts work fine regardless of whether the user is using bash or zsh.

#!/bin/bash
# read-string.sh
# eg: my_string=$(./read-string.sh); echo "my_string: $my_string"

# bash `read` manual - https://ss64.com/bash/read.html
# 
# read [-ers] [-a aname]  [-d delim] [-i text] [-n nchars]
#    [-N nchars] [-p prompt] [-r] [-s] [-t timeout] [-u fd]
#       [name...]
# 
#  -r        Do not treat a Backslash as an escape character.  The backslash is considered to be part
#            of the line. In particular, a backslash-newline pair can not be used as a line continuation.
#            Without this option, any backslashes in the input will be discarded.
#            You should almost always use the -r option with read.

# zsh `read` manual - http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html#index-read
# 
# read [ -rszpqAclneE ] [ -t [ num ] ] [ -k [ num ] ] [ -d delim ]
#     [ -u n ] [ name[?prompt] ] [ name ... ]
# 
# -r         Raw mode: a ‘\’ at the end of a line does not signify line continuation and backslashes in the line
#            don’t quote the following character and are not removed.

if [ -n "$ZSH_VERSION" ]; then
  read -r "answer?"
else
  read -r answer
fi
echo "$answer"
#!/bin/bash
# eg: my_char=$(read-char.sh); echo "my_char: $my_char"

# bash `read` manual - https://ss64.com/bash/read.html
# 
# read [-ers] [-a aname]  [-d delim] [-i text] [-n nchars]
#    [-N nchars] [-p prompt] [-r] [-s] [-t timeout] [-u fd]
#       [name...]
# 
#  -r        Do not treat a Backslash as an escape character.  The backslash is considered to be part
#            of the line. In particular, a backslash-newline pair can not be used as a line continuation.
#            Without this option, any backslashes in the input will be discarded.
#            You should almost always use the -r option with read.
#  -n nchars read returns after reading nchars characters rather than waiting for a complete line of input.

# zsh `read` manual - http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html#index-read
# 
# read [ -rszpqAclneE ] [ -t [ num ] ] [ -k [ num ] ] [ -d delim ]
#     [ -u n ] [ name[?prompt] ] [ name ... ]
# 
# -q         Read only one character from the terminal and set name to ‘y’ if this character was ‘y’ or ‘Y’
#            and to ‘n’ otherwise. With this flag set the return status is zero only if the character was ‘y’ or ‘Y’.
#            This option may be used with a timeout (see -t); if the read times out, or encounters end of file,
#            status 2 is returned. Input is read from the terminal unless one of -u or -p is present.
#            This option may also be used within zle widgets.
# -r         Raw mode: a ‘\’ at the end of a line does not signify line continuation and backslashes in the line
#            don’t quote the following character and are not removed.

if [ -n "$ZSH_VERSION" ]; then
  read -r -q "answer?"
else
  read -r -n 1 answer
fi
echo "$answer"

Thanks to Olaf for his original answer.

Upvotes: 3

Ben
Ben

Reputation: 6348

I'm adding this answer because every time you want to ask the user for confirmation, you also want to act on it. here's a function that prompts with read -q (thanks, other answers!) and branches on the result to do what you want (in this case, git stuff):

git_commit_and_pull() {
    # http://zsh.sourceforge.net/Doc/Release/Shell-Builtin-Commands.html#index-read
    if read -q "choice?Press Y/y to continue with commit and pull: "; then
        git add . && git commit -m 'haha git goes brrr' && git pull
    else
        echo
        echo "'$choice' not 'Y' or 'y'. Exiting..."
    fi
}

Upvotes: 32

GoZoner
GoZoner

Reputation: 70165

See ZSH Manual for documentation of ZSH's read. Try:

read REPLY\?"This is the question I want to ask?"

Upvotes: 12

Olaf Dietsche
Olaf Dietsche

Reputation: 74038

From zsh - read

If the first argument contains a ‘?’, the remainder of this word is used as a prompt on standard error when the shell is interactive.

You must quote the entire argument

read -q "REPLY?This is the question I want to ask?"

this will prompt you with This is the question I want to ask? and return the character pressed in REPLY.

If you don't quote the question mark, zsh tries to match the argument as a filename. And if it doesn't find any matching filename, it complains with no matches found.

Upvotes: 59

Related Questions