A Perl function to prompt users during interactive command-line input

For many Unix users, Perl has replaced the Bourne shell, C shell, and Korn shell as a preferred programming language for many scripting duties and short programs.

Used as a scripting language, Perl programs often require you to prompt the user to enter some type of input. I've found that instead of writing separate "prompting" code in each of my Perl scripts, it's easier to create one prompting routine that can be used in almost all Perl programs.

In this article we'll take a look at a promptUser() function and demonstrate it's use in a short driver program.

Capabilities of the Perl promptUser() function

Before getting started, let's summarize the capabilities we want in the promptUser() function:

  • It prompts the user for input, and waits until they type their response and hit the [Enter] key.
  • It lets you specify the prompt to display to the user.
  • It lets you alternately specify a default value for the user input; this lets the user just hit the [Enter] key if they want to accept the default value.
  • When it exits, it returns either (a) the default value or (b) the user input back to the calling program, depending on what the user entered.

The blackbox view of the function

Okay, now let's take a look at the function from a "blackbox" perspective - what goes in, and what comes out. 

Example 1 - Providing only the $promptString as input

The promptUser() function accepts one or two arguments as input. The first argument is the text string that you want to use as a prompt. This can often be something like "Enter your age", or "Enter the home directory". Here's how you'd call the function in a Perl program to prompt the user to enter their home directory:

$homeDir = &promptUser("Enter the home directory");

With this code, the user would see this prompt:

Enter the home directory: _

Example 2 - Providing both the $promptString and a $defaultValue as input arguments

In a second example, suppose you're writing a Perl program to install user accounts. Assuming you already know that a user's name is fred, and the normal home directory for a user named fred would be /home/fred, you might call the promptUser() function with these two arguments:

$homeDir = &promptUser("Enter the home directory", "/home/fred");

In this example, you're providing both (a) the prompt and (b) the default value (/home/fred) to the promptUser() function. When the user sees the prompt on screen it will now look like this:

Enter the home directory [/home/fred]: _

The Perl command line input source code

Okay, now that you've seen how the promptUser function works, and what it's output looks like, let's look at the code. The source code for the promptUser function and a simple driver program is shown in Listing 1. (A driver program is often used to test functions and methods when they're first created, or when they're being debugged.) Because the source code is well-documented, we'll only discuss its key features.

#!/usr/local/bin/perl
#-------------------------------------------------------------------------#
# promptUser, a Perl subroutine to prompt a user for input.
# Copyright 2010 Alvin Alexander, http://www.devdaily.com
# This code is shared here under the 
# Creative Commons Attribution-ShareAlike Unported 3.0 license.
# See http://creativecommons.org/licenses/by-sa/3.0/ for more information.
#-------------------------------------------------------------------------#

#------------------------------------------#
#  "driver" program to test &promptUser()  #
#------------------------------------------#

$username = &promptUser("Enter the username ");
$password = &promptUser("Enter the password ");
$homeDir  = &promptUser("Enter the home directory ", "/home/$username");

print "$username, $password, $homeDir\n";


#----------------------------(  promptUser  )-----------------------------#
#                                                                         #
#  FUNCTION:	promptUser                                                #
#                                                                         #
#  PURPOSE:	Prompt the user for some type of input, and return the    #
#		input back to the calling program.                        #
#                                                                         #
#  ARGS:	$promptString - what you want to prompt the user with     #
#		$defaultValue - (optional) a default value for the prompt #
#                                                                         #
#-------------------------------------------------------------------------#

sub promptUser {

   #-------------------------------------------------------------------#
   #  two possible input arguments - $promptString, and $defaultValue  #
   #  make the input arguments local variables.                        #
   #-------------------------------------------------------------------#

   local($promptString,$defaultValue) = @_;

   #-------------------------------------------------------------------#
   #  if there is a default value, use the first print statement; if   #
   #  no default is provided, print the second string.                 #
   #-------------------------------------------------------------------#

   if ($defaultValue) {
      print $promptString, "[", $defaultValue, "]: ";
   } else {
      print $promptString, ": ";
   }

   $| = 1;               # force a flush after our print
   $_ = <STDIN>;         # get the input from STDIN (presumably the keyboard)


   #------------------------------------------------------------------#
   # remove the newline character from the end of the input the user  #
   # gave us.                                                         #
   #------------------------------------------------------------------#

   chomp;

   #-----------------------------------------------------------------#
   #  if we had a $default value, and the user gave us input, then   #
   #  return the input; if we had a default, and they gave us no     #
   #  no input, return the $defaultValue.                            #
   #                                                                 # 
   #  if we did not have a default value, then just return whatever  #
   #  the user gave us.  if they just hit the <enter> key,           #
   #  the calling routine will have to deal with that.               #
   #-----------------------------------------------------------------#

   if ("$defaultValue") {
      return $_ ? $_ : $defaultValue;    # return $_ if it has a value
   } else {
      return $_;
   }
}

Listing 1 (above): The promptUser function can be used to prompt users for input from many Perl programs.

The driver program is shown at the beginning of the source code. In our driver, the promptUser function is called three times - twice without a default value, and once with a default value specified.

Next comes the promptUser function. As discussed, the promptUser function accepts two arguments as input: $promptString and $defaultValue. These two values are converted to local values when they're created in the first step of the function. As we all know, this is much better than using global variables.

Next, the prompt is displayed to the user. If a $defaultValue was provided, the first print statement will be used; otherwise the second print statement will be used. (Remember that $defaultValue is treated as false if it's value is zero or null.)

Then we set the special variable $| to a non-zero value to flush the output, and then read the input from STDIN ("standard input"). Assuming that this function is used in an interactive program, STDIN will be the user's keyboard. After the data is read, the chomp function is called to remove any newline characters at the end of the user input.

At the end of the function we again check the value of $defaultValue. If a default value was provided, we check the user input. If the user just hit the [Enter] key, we return the value of $defaultValue. If the user entered some type of input, we'll return $_ instead. That's what happens in this syntax:

return $_ ? $_ : defaultValue;

Now, if a $defaultValue was not provided when this function was called, we simply return $_ to the calling program. In this case it's possible to return a null value to the calling program, so the calling program must be smart enough to deal with that possibility.

Permalink

1) This doesnt compile with strict, and the driver parts do not work with strict and warnings.

2) The argument assignment from local is part of this, the scope needs to be my() instead of local and it will stay strict.

3) Explicitly assigning to the magic variable $_ seems like a questionable decision. Generally $_ just retains the value of the last operation, so you can use syntax like chomp; or print;

Unfortunately, like not using strict, this becomes fraught with issues. Relying on a magic value like that makes the body of this function very brittle. Using $_ = ; chomp; completely couples the two lines together, where you could have actually just made an assignment and safely chomped it like this:
chomp(my $input = ); The other way means that if any debugging or functional code is put between the two lines that chomp will likely operate on something other than what you want.

4) You quote $default value in the second conditional, but not in the first one, the string coercion is unnecessary and is inconsistent with the earlier output statements.

5) $| = 1 should be local and isn't, also isn't doing anything in this context, since you are only printing before it. It also probably doesnt do what you think it does, if you are trying to print the line, properly flushed with a newline, you can use the -l flag, but that is a script level setting. This particular idiom is just line noise.

6) Your conditional return is completely unnecessary, 'return $_ : $_ : $default' will work fine.

With that in mind, this is an equivalent function, with strict, warnings, and no magic variables:

#!/usr/bin/perl -w
use strict;

sub promptUser {
my($prompt, $default) = @_;
my $defaultValue = $default ? "[$default]" : "";
print "$prompt $defaultValue: ";

chomp(my $input = );

return $input ? $input : $default;
}

A prompt with no default can still return nil, but that was an issue in the original script.

Thanks,
liam

In reply to by liam (not verified)

Permalink

@liam
Point 3 had <STDIN> stripped out of it. It appears before chomp.

Point 6 has a typo, the first : should be a ?

Formatting, line 8 in the script should be:
chomp(my $input = <STDIN>);

Thanks for the comments and the updates to the code. When I first wrote this I wasn't too worried about strict checking and warnings, but those are definitely areas to be concerned with when sharing functions like this.

Here's the promptUser function you wrote, with the formatting fixes:

#!/usr/bin/perl -w
use strict;

sub promptUser {
  my($prompt, $default) = @_;
  my $defaultValue = $default ? "[$default]" : "";
  print "$prompt $defaultValue: ";
  chomp(my $input = <STDIN>);
  return $input ? $input : $default;
}

Thanks again for the great feedback.

Add new comment

The content of this field is kept private and will not be shown publicly.

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.