Skip to content

Instantly share code, notes, and snippets.

@r0l1
Last active November 25, 2023 19:12
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save r0l1/3dcbb0c8f6cfe9c66ab8008f55f8f28b to your computer and use it in GitHub Desktop.
Save r0l1/3dcbb0c8f6cfe9c66ab8008f55f8f28b to your computer and use it in GitHub Desktop.
Go (golang): How to ask for user confirmation via command line
/* MIT License
*
* Copyright (c) 2017 Roland Singer [roland.singer@desertbit.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.
*/
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
// askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
// confirmations. If the input is not recognized, it will ask again. The function does not return
// until it gets a valid response from the user.
func askForConfirmation(s string) bool {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Printf("%s [y/n]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
}
}
func main() {
c := askForConfirmation("Do you really want to reset your system?")
if c {
fmt.Println("Sorry dude, I need root rights...")
return
}
}
@florian-zeidler
Copy link

That doesn't work if the user enters garbage, like oh yeah!

@marians
Copy link

marians commented Jan 6, 2017

@das-vinculum Yes, it should. The user will be asked again.

@r0l1
Copy link
Author

r0l1 commented Jan 11, 2017

@das-vinculum:

If the input is not recognized, it will ask again. The function does not return until it gets a valid response from the user.

@dwin
Copy link

dwin commented Jan 12, 2017

It should probably be made to fail after several tries.

@ryanbaer
Copy link

My approach is a little more strict (anything other than 'y' is false), but could be tweaked to only accept 'y' or 'n'.

// confirm displays a prompt `s` to the user and returns a bool indicating yes / no
// If the lowercased, trimmed input begins with anything other than 'y', it returns false
// It accepts an int `tries` representing the number of attempts before returning false
func confirm(s string, tries int) bool {
	r := bufio.NewReader(os.Stdin)

	for ; tries > 0; tries-- {
		fmt.Printf("%s [y/n]: ", s)

		res, err := r.ReadString('\n')
		if err != nil {
			log.Fatal(err)
		}

		// Empty input (i.e. "\n")
		if len(res) < 2 {
			continue
		}

		return strings.ToLower(strings.TrimSpace(res))[0] == 'y'
	}

	return false
}

@davideasaf
Copy link

davideasaf commented Mar 7, 2017

Just for learning sake, How would you write a unit test for something like this?

@mylons
Copy link

mylons commented Mar 9, 2017

you'd have to mock out the call out ReadString for testing

@petergtz
Copy link

@davideasaf You can slightly change the function signature:

func askForConfirmation(s string, in io.Reader) bool {
    reader := bufio.NewReader(in)
    ...
}

That will allow you to pass in a mock reader in unit tests and os.Stdin in your production code.

@BennetE
Copy link

BennetE commented Oct 5, 2023

My approach is a little more strict (anything other than 'y' is false), but could be tweaked to only accept 'y' or 'n'.

// confirm displays a prompt `s` to the user and returns a bool indicating yes / no
// If the lowercased, trimmed input begins with anything other than 'y', it returns false
// It accepts an int `tries` representing the number of attempts before returning false
func confirm(s string, tries int) bool {
	r := bufio.NewReader(os.Stdin)

	for ; tries > 0; tries-- {
		fmt.Printf("%s [y/n]: ", s)

		res, err := r.ReadString('\n')
		if err != nil {
			log.Fatal(err)
		}

		// Empty input (i.e. "\n")
		if len(res) < 2 {
			continue
		}

		return strings.ToLower(strings.TrimSpace(res))[0] == 'y'
	}

	return false
}

@ryanbaer Works great, but I'm getting an error when just pressing the enter key. Shouldn't it be if len(res) < 3 { continue } as "\n" already has a length of 2?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment