string select and append to another string - linux

I have a file (FreshPIN.txt) contain lots of pin code in each line; I need a bash script to select one of the pin, print it out, and then remove it from the source file, adding it to end of another file (usedPIN.txt).
FreshpPIN.txt is like:
========
1111111111111111
2222222222222222
3333333333333333
....
nnnnnnnnnnnnnnnn
========
before it prints, I should be asked to enter a number from 0 to 31 and put the number in the command below:
at&g**00**=xtd*788*1111111111111111#
in above example at&g and =xtd*788* should be stable in all output commands.

fresh=FreshPIN.txt
used=usedPin.txt
echo "Please key in"
read key
pin=`head -1 "$fresh"`
printf '%s\n' "$pin" >>"$used"
sed -i~ 1d "$fresh"
printf 'at&g%s=xtd*788*%s\n' "$key" "$pin"

How about this?
#!/bin/bash
fresh=FreshpPIN.txt
used=usedPIN.txt
max=31
die() {
echo >&2 "$#"
exit 1
}
# Get a random pin
pin=$(sed -n '/[[:digit:]]\+/p' -- "$fresh" | shuf -n1)
[[ "$pin" ]] || die "No more pins in file \`$fresh'"
echo "Pin chosen: $pin"
# Prompt user:
while read -e -r -p "Enter a number between 0 and $max (q to quit): " n; do
if [[ "$n" = q ]]; then
echo "Aborting. Pin $pin remains in file \`$fresh'."
exit 0
elif [[ "$n" != +([[:digit:]]) ]]; then
echo "Not a valid number. Try again."
elif ((10#$n>max)); then
echo "Number must be between 0 and $max. Try again."
else
break
fi
done
# Guard if read fails (e.g., if user presses Ctrl-D)
[[ "$n" ]] || die "Something went wrong."
# Delete this pin from file
ed -s -- "$fresh" <<EOF
/^$pin\$/d
wq
EOF
# Save pin in file
printf >> "$used" "%s\n" "$pin"
# Output:
printf "at&g**%02d**=xtd*788*%s\n" "$((10#$n))" "$pin"
It's quite robust (the user must really enter a number between 0 and 31, and it won't be messed up if user enters, e.g., 09). Uses ed to delete old pin from file FreshpPIN.txt: very efficient (no auxiliary file or ugly stuff using sed -i). Uses good bash practice overall. Uses shuf to get a random pin (don't need to compute the number of lines and hack ugly stuff around to get a random pin). sed is used to select only pins from file FreshpPIN.txt, so you can leave your header, comment, etc. in there.

Related

Bash script that allows one word as user input

Made a script that the user gives a "parameter" and it prints out if it is a file, directory or non of them. This is it :
#!/bin/bash
read parametros
for filename in *
do
if [ -f "$parametros" ];
then
echo "$parametros is a file"
elif [ -d "$parametros" ];
then
echo "$parametros is a directory"
else
echo " There is not such file or directory"
fi
exit
done
Altough i want the user to be allowed to give only one word as a parameter. How do i make this happen ? (For example if user press space after first word there would be an error message showing "wrong input")
#!/bin/bash
read parametros
if [[ "$parametros" = *[[:space:]]* ]]
then
echo "wrong input"
elif [[ -f "$parametros" ]]
then
echo "$parametros is a file"
elif [[ -d "$parametros" ]]
then
echo "$parametros is a directory"
else
echo " There is not such file or directory"
fi
See http://mywiki.wooledge.org/BashFAQ/031 for the difference between [...] and [[...]].
You have to use the $#. It gives the number of the parameters.
The code will be something like:
if [ "$#" -ne 1 ]; then
printf 'ERROR!\n'
exit 1
fi
First, I'm curious why you want to restrict to one word - a file or directory could have spaces in it, but maybe you are preventing that somehow in your context.
Here are a few ways you could approach it:
Validate the input after they enter it - check if it has any spaces, eg: if [[ "parametros" == *" " ]]; then...
Get one character at a time in a while loop, eg with: read -n1 char
Show an error if it's a space
Break the loop if it's 'enter'
Build up the overall string from the entered characters
1 is obviously much simpler, but maybe 2 is worth the effort for the instant feedback that you are hoping for?

How to add an unknown amount of numbers from user input in Linux shell script

I'm trying to add up all numbers inputted by a user, however there is no limit on how many numbers a user can input for the addition. How do I code this in linux shell script?
I have this so far:
firstNumber=0
secondNumber=0
number=0
echo Please enter two numbers to add up
read firstNumber
read secondNumber
echo Would you like to keep adding numbers? YES OR NO
read answer
if answer = YES
then
echo Please add another number
read number
echo $(($firstNumber +$secondNumber + $number))
fi
while answer = NO
do
echo $(($firstNumber + $secondNumber))
done
as #dash-o recommended, a simple entry sequence ended with ENTER is the most simple approach:
#!/usr/bin/env sh
sum=0
echo "Please enter integer numbers to add, or just RETURN to end."
while read -r number && [ -n "$number" ]; do
if [ "$number" -eq "$number" ] 2>/dev/null; then
sum=$((sum + number))
echo "Sum is: $sum"
else
echo "$number is not a valid integer. Try again..." >&2
fi
done
Or to allow multiple integers entry per line:
#!/usr/bin/env sh
# Save the shell's options state
shelloptions="$(set +o)"
# Disable globbing to prevent filename expansion in parameters
set -o noglob
sum=0
echo "Please enter integer numbers to add, or RETURN to end."
# Read lines until empty REPLY
while read -r && [ -n "$REPLY" ]; do
# Split $REPLY as parameters
# Globbing is turned-off so filenames will not mess with entries
# shellcheck disable=SC2086 # Explicitly intended word splitting
set -- $REPLY
# Iterate numbers from the parameters array
for number in "$#"; do
# If $number is a valid integer
if [ "$number" -eq "$number" ] 2>/dev/null; then
sum=$((sum + number))
else
echo "$number is not a valid integer." >&2
fi
done
echo "Sum is: $sum"
done
# Restore the shell's options state
eval "$shelloptions"

Bash scripting: why is the last line missing from this file append?

I'm writing a bash script to read a set of files line by line and perform some edits. To begin with, I'm simply trying to move the files to backup locations and write them out as-is, to test the script is working. However, it is failing to copy the last line of each file. Here is the snippet:
while IFS= read -r line
do
echo "Line is ***$line***"
echo "$line" >> $POM
done < $POM.backup
I obviously want to preserve whitespace when I copy the files, which is why I have set the IFS to null. I can see from the output that the last line of each file is being read, but it never appears in the output.
I've also tried an alternative variation, which does print the last line, but adds a newline to it:
while IFS= read -r line || [ -n "$line" ]
do
echo "Line is ***$line***"
echo "$line" >> $POM
done < $POM.backup
What is the best way to do this do this read-write operation, to write the files exactly as they are, with the correct whitespace and no newlines added?
The command that is adding the line feed (LF) is not the read command, but the echo command. read does not return the line with the delimiter still attached to it; rather, it strips the delimiter off (that is, it strips it off if it was present in the line, IOW, if it just read a complete line).
So, to solve the problem, you have to use echo -n to avoid adding back the delimiter, but only when you have an incomplete line.
Secondly, I've found that when providing read with a NAME (in your case line), it trims leading and trailing whitespace, which I don't think you want. But this can be solved by not providing a NAME at all, and using the default return variable REPLY, which will preserve all whitespace.
So, this should work:
#!/bin/bash
inFile=in;
outFile=out;
rm -f "$outFile";
rc=0;
while [[ $rc -eq 0 ]]; do
read -r;
rc=$?;
if [[ $rc -eq 0 ]]; then ## complete line
echo "complete=\"$REPLY\"";
echo "$REPLY" >>"$outFile";
elif [[ -n "$REPLY" ]]; then ## incomplete line
echo "incomplete=\"$REPLY\"";
echo -n "$REPLY" >>"$outFile";
fi;
done <"$inFile";
exit 0;
Edit: Wow! Three excellent suggestions from Charles Duffy, here's an updated script:
#!/bin/bash
inFile=in;
outFile=out;
while { read -r; rc=$?; [[ $rc -eq 0 || -n "$REPLY" ]]; }; do
if [[ $rc -eq 0 ]]; then ## complete line
echo "complete=\"$REPLY\"";
printf '%s\n' "$REPLY" >&3;
else ## incomplete line
echo "incomplete=\"$REPLY\"";
printf '%s' "$REPLY" >&3;
fi;
done <"$inFile" 3>"$outFile";
exit 0;
After review i wonder if :
{
line=
while IFS= read -r line
do
echo "$line"
line=
done
echo -n "$line"
} <$INFILE >$OUTFILE
is juts not enough...
Here my initial proposal :
#!/bin/bash
INFILE=$1
if [[ -z $INFILE ]]
then
echo "[ERROR] missing input file" >&2
exit 2
fi
OUTFILE=$INFILE.processed
# a way to know if last line is complete or not :
lastline=$(tail -n 1 "$INFILE" | wc -l)
if [[ $lastline == 0 ]]
then
echo "[WARNING] last line is incomplete -" >&2
fi
# we add a newline ANYWAY if it was complete, end of file will be seen as ... empty.
echo | cat $INFILE - | {
first=1
while IFS= read -r line
do
if [[ $first == 1 ]]
then
echo "First Line is ***$line***" >&2
first=0
else
echo "Next Line is ***$line***" >&2
echo
fi
echo -n "$line"
done
} > $OUTFILE
if diff $OUTFILE $INFILE
then
echo "[OK]"
exit 0
else
echo "[KO] processed file differs from input"
exit 1
fi
Idea is to always add a newline at the end of file and to print newlines only BETWEEN lines that are read.
This should work for quite all text files given they are not containing 0 byte ie \0 character, in which case 0 char byte will be lost.
Initial test can be used to decided whether an incomplete text file is acceptable or not.
Add a new line if line is not a line. Like this:
while IFS= read -r line
do
echo "Line is ***$line***";
printf '%s' "$line" >&3;
if [[ ${line: -1} != '\n' ]]
then
printf '\n' >&3;
fi
done < $POM.backup 3>$POM

Hiding user input on terminal in Linux script

I have bash script like the following:
#!/bin/bash
echo "Please enter your username";
read username;
echo "Please enter your password";
read password;
I want that when the user types the password on the terminal, it should not be displayed (or something like *******) should be displayed). How do I achieve this?
Just supply -s to your read call like so:
$ read -s PASSWORD
$ echo $PASSWORD
Update
In case you want to get fancy by outputting an * for each character they type, you can do something like this (using andreas' read -s solution):
unset password;
while IFS= read -r -s -n1 pass; do
if [[ -z $pass ]]; then
echo
break
else
echo -n '*'
password+=$pass
fi
done
Without being fancy
echo "Please enter your username";
read username;
echo "Please enter your password";
stty -echo
read password;
stty echo
you can use stty to disable echo
this solution works without bash or certain features from read
stty_orig=$(stty -g)
stty -echo
read password
stty $stty_orig
If you use this in a shell script then also set an exit handler which restores echo:
#! /bin/sh
stty_orig=$(stty -g)
trap "stty ${stty_orig}" EXIT
stty -echo
...
this is to make sure echo is restored regardless of how the script exits. otherwise the echo will stay off if the script errors out.
to turn echo back on manually type the following command
stty echo
you will have to type blindly because you do not see what you type.
i suggest to press ctrl+c first to clear anything else you might have typed before.
trivia
echo means to echo your typed input back to your screen.
this is from the time we worked on teletypewriters (that is what the tty means). a teletypewriter is like a typewriter but connected to another teletypewriter or computer. typically via telephone cable.
the workflow on a teletypewriter is roughly as follows: you type in your command (or message for the other side). then the teletypewriter will print the response from the other side.
when you work on a teletypewriter you see your input as you type. this is because the teletypewriter is also a typewriter and as such prints the characters as you press them.
when teletypewriters where replaced by screens there was no longer a typewriter which types your input. instead we had to deliberate code an "echo" function which prints your input as you type.
i do not know whether stty -echo also disabled printing on a teletypewriter.
see here for a teletypewriter in action: https://www.youtube.com/watch?v=2XLZ4Z8LpEE (first part is restoration. action starting at about 12 minutes in)
more teletypewriter restoration: https://www.youtube.com/playlist?list=PL-_93BVApb5-9eQLTCk9xx16RAGEYHH1q
Here's a variation on #SiegeX's excellent *-printing solution for bash with support for backspace added; this allows the user to correct their entry with the backspace key (delete key on a Mac), as is typically supported by password prompts:
#!/usr/bin/env bash
password=''
while IFS= read -r -s -n1 char; do
[[ -z $char ]] && { printf '\n' >/dev/tty; break; } # ENTER pressed; output \n and break.
if [[ $char == $'\x7f' ]]; then # backspace was pressed
# Remove last char from output variable.
[[ -n $password ]] && password=${password%?}
# Erase '*' to the left.
printf '\b \b' >/dev/tty
else
# Add typed char to output variable.
password+=$char
# Print '*' in its stead.
printf '*' >/dev/tty
fi
done
Note:
As for why pressing backspace records character code 0x7f: "In modern systems, the backspace key is often mapped to the delete character (0x7f in ASCII or Unicode)" https://en.wikipedia.org/wiki/Backspace
\b \b is needed to give the appearance of deleting the character to the left; just using \b moves the cursor to the left, but leaves the character intact (nondestructive backspace). By printing a space and moving back again, the character appears to have been erased (thanks, The "backspace" escape character '\b': unexpected behavior?).
In a POSIX-only shell (e.g., sh on Debian and Ubuntu, where sh is dash), use the stty -echo approach (which is suboptimal, because it prints nothing), because the read builtin will not support the -s and -n options.
A bit different from (but mostly like) #lesmana's answer
stty -echo
read password
stty echo
simply: hide echo
do your stuff
show echo
I always like to use Ansi escape characters:
echo -e "Enter your password: \x1B[8m"
echo -e "\x1B[0m"
8m makes text invisible and 0m resets text to "normal." The -e makes Ansi escapes possible.
The only caveat is that you can still copy and paste the text that is there, so you probably shouldn't use this if you really want security.
It just lets people not look at your passwords when you type them in. Just don't leave your computer on afterwards. :)
NOTE:
The above is platform independent as long as it supports Ansi escape sequences.
However, for another Unix solution, you could simply tell read to not echo the characters...
printf "password: "
let pass $(read -s)
printf "\nhey everyone, the password the user just entered is $pass\n"
Here is a variation of #SiegeX's answer which works with traditional Bourne shell (which has no support for += assignments).
password=''
while IFS= read -r -s -n1 pass; do
if [ -z "$pass" ]; then
echo
break
else
printf '*'
password="$password$pass"
fi
done
Get Username and password
Make it more clear to read but put it on a better position over the screen
#!/bin/bash
clear
echo
echo
echo
counter=0
unset username
prompt=" Enter Username:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
if [[ $char == $'\0' ]]; then
break
elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then
prompt=$'\b \b'
username="${username%?}"
counter=$((counter-1))
elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then
prompt=''
continue
else
counter=$((counter+1))
prompt="$char"
username+="$char"
fi
done
echo
unset password
prompt=" Enter Password:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
if [[ $char == $'\0' ]]; then
break
elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then
prompt=$'\b \b'
password="${password%?}"
counter=$((counter-1))
elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then
echo
prompt=" Enter Password:"
continue
else
counter=$((counter+1))
prompt='*'
password+="$char"
fi
done
A variation on both #SiegeX and #mklement0's excellent contributions: mask user input; handle backspacing; but only backspace for the length of what the user has input (so we're not wiping out other characters on the same line) and handle control characters, etc... This solution was found here after so much digging!
#!/bin/bash
#
# Read and echo a password, echoing responsive 'stars' for input characters
# Also handles: backspaces, deleted and ^U (kill-line) control-chars
#
unset PWORD
PWORD=
echo -n 'password: ' 1>&2
while true; do
IFS= read -r -N1 -s char
# Note a NULL will return a empty string
# Convert users key press to hexadecimal character code
code=$(printf '%02x' "'$char") # EOL (empty char) -> 00
case "$code" in
''|0a|0d) break ;; # Exit EOF, Linefeed or Return
08|7f) # backspace or delete
if [ -n "$PWORD" ]; then
PWORD="$( echo "$PWORD" | sed 's/.$//' )"
echo -n $'\b \b' 1>&2
fi
;;
15) # ^U or kill line
echo -n "$PWORD" | sed 's/./\cH \cH/g' >&2
PWORD=''
;;
[01]?) ;; # Ignore ALL other control characters
*) PWORD="$PWORD$char"
echo -n '*' 1>&2
;;
esac
done
echo
echo $PWORD

How do I prompt for Yes/No/Cancel input in a Linux shell script?

I want to pause input in a shell script, and prompt the user for choices.
The standard Yes, No, or Cancel type question.
How do I accomplish this in a typical bash prompt?
The simplest and most widely available method to get user input at a shell prompt is the read command. The best way to illustrate its use is a simple demonstration:
while true; do
read -p "Do you wish to install this program? " yn
case $yn in
[Yy]* ) make install; break;;
[Nn]* ) exit;;
* ) echo "Please answer yes or no.";;
esac
done
Another method, pointed out by Steven Huwig, is Bash's select command. Here is the same example using select:
echo "Do you wish to install this program?"
select yn in "Yes" "No"; do
case $yn in
Yes ) make install; break;;
No ) exit;;
esac
done
With select you don't need to sanitize the input – it displays the available choices, and you type a number corresponding to your choice. It also loops automatically, so there's no need for a while true loop to retry if they give invalid input.
Also, Léa Gris demonstrated a way to make the request language agnostic in her answer. Adapting my first example to better serve multiple languages might look like this:
set -- $(locale LC_MESSAGES)
yesexpr="$1"; noexpr="$2"; yesword="$3"; noword="$4"
while true; do
read -p "Install (${yesword} / ${noword})? " yn
if [[ "$yn" =~ $yesexpr ]]; then make install; exit; fi
if [[ "$yn" =~ $noexpr ]]; then exit; fi
echo "Answer ${yesword} / ${noword}."
done
Obviously other communication strings remain untranslated here (Install, Answer) which would need to be addressed in a more fully completed translation, but even a partial translation would be helpful in many cases.
Finally, please check out the excellent answer by F. Hauri.
At least five answers for one generic question.
Depending on
posix compliant: could work on poor systems with generic shell environments
bash specific: using so called bashisms
and if you want
simple ``in line'' question / answer (generic solutions)
pretty formatted interfaces, like ncurses or more graphical using libgtk or libqt...
use powerful readline history capability
1. POSIX generic solutions
You could use the read command, followed by if ... then ... else:
printf 'Is this a good question (y/n)? '
read answer
# if echo "$answer" | grep -iq "^y" ;then
if [ "$answer" != "${answer#[Yy]}" ] ;then # this grammar (the #[] operator) means that the variable $answer where any Y or y in 1st position will be dropped if they exist.
echo Yes
else
echo No
fi
(Thanks to Adam Katz's comment: Replaced the test above with one that is more portable and avoids one fork:)
POSIX, but single key feature
But if you don't want the user to have to hit Return, you could write:
(Edited: As #JonathanLeffler rightly suggest, saving stty's configuration could be better than simply force them to sane.)
printf 'Is this a good question (y/n)? '
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if [ "$answer" != "${answer#[Yy]}" ];then
echo Yes
else
echo No
fi
Note: This was tested under sh, bash, ksh, dash and busybox!
Same, but waiting explicitly for y or n:
#/bin/sh
printf 'Is this a good question (y/n)? '
old_stty_cfg=$(stty -g)
stty raw -echo
answer=$( while ! head -c 1 | grep -i '[ny]' ;do true ;done )
stty $old_stty_cfg
if [ "$answer" != "${answer#[Yy]}" ];then
echo Yes
else
echo No
fi
Using dedicated tools
There are many tools which were built using libncurses, libgtk, libqt or other graphical libraries. For example, using whiptail:
if whiptail --yesno "Is this a good question" 20 60 ;then
echo Yes
else
echo No
fi
Depending on your system, you may need to replace whiptail with another similiar tool:
dialog --yesno "Is this a good question" 20 60 && echo Yes
gdialog --yesno "Is this a good question" 20 60 && echo Yes
kdialog --yesno "Is this a good question" 20 60 && echo Yes
where 20 is height of dialog box in number of lines and 60 is width of the dialog box. These tools all have near same syntax.
DIALOG=whiptail
if [ -x /usr/bin/gdialog ] ;then DIALOG=gdialog ; fi
if [ -x /usr/bin/xdialog ] ;then DIALOG=xdialog ; fi
...
$DIALOG --yesno ...
2. Bash specific solutions
Basic in line method
read -p "Is this a good question (y/n)? " answer
case ${answer:0:1} in
y|Y )
echo Yes
;;
* )
echo No
;;
esac
I prefer to use case so I could even test for yes | ja | si | oui if needed...
in line with single key feature
Under bash, we can specify the length of intended input for for the read command:
read -n 1 -p "Is this a good question (y/n)? " answer
Under bash, read command accepts a timeout parameter, which could be useful.
read -t 3 -n 1 -p "Is this a good question (Y/n)? " answer
[ -z "$answer" ] && answer="Yes" # if 'yes' have to be default choice
Timeout with countdown:
i=6 ;while ((i-->1)) &&
! read -sn 1 -t 1 -p $'\rIs this a good question (Y/n)? '$i$'..\e[3D' answer;do
:;done ;[[ $answer == [nN] ]] && answer=No || answer=Yes ;echo "$answer "
3. Some tricks for dedicated tools
More sophisticated dialog boxes, beyond simple yes - no purposes:
dialog --menu "Is this a good question" 20 60 12 y Yes n No m Maybe
Progress bar:
dialog --gauge "Filling the tank" 20 60 0 < <(
for i in {1..100};do
printf "XXX\n%d\n%(%a %b %T)T progress: %d\nXXX\n" $i -1 $i
sleep .033
done
)
Little demo:
#!/bin/sh
while true ;do
[ -x "$(which ${DIALOG%% *})" ] || DIALOG=dialog
DIALOG=$($DIALOG --menu "Which tool for next run?" 20 60 12 2>&1 \
whiptail "dialog boxes from shell scripts" >/dev/tty \
dialog "dialog boxes from shell with ncurses" \
gdialog "dialog boxes from shell with Gtk" \
kdialog "dialog boxes from shell with Kde" ) || break
clear;echo "Choosed: $DIALOG."
for i in `seq 1 100`;do
date +"`printf "XXX\n%d\n%%a %%b %%T progress: %d\nXXX\n" $i $i`"
sleep .0125
done | $DIALOG --gauge "Filling the tank" 20 60 0
$DIALOG --infobox "This is a simple info box\n\nNo action required" 20 60
sleep 3
if $DIALOG --yesno "Do you like this demo?" 20 60 ;then
AnsYesNo=Yes; else AnsYesNo=No; fi
AnsInput=$($DIALOG --inputbox "A text:" 20 60 "Text here..." 2>&1 >/dev/tty)
AnsPass=$($DIALOG --passwordbox "A secret:" 20 60 "First..." 2>&1 >/dev/tty)
$DIALOG --textbox /etc/motd 20 60
AnsCkLst=$($DIALOG --checklist "Check some..." 20 60 12 \
Correct "This demo is useful" off \
Fun "This demo is nice" off \
Strong "This demo is complex" on 2>&1 >/dev/tty)
AnsRadio=$($DIALOG --radiolist "I will:" 20 60 12 \
" -1" "Downgrade this answer" off \
" 0" "Not do anything" on \
" +1" "Upgrade this anser" off 2>&1 >/dev/tty)
out="Your answers:\nLike: $AnsYesNo\nInput: $AnsInput\nSecret: $AnsPass"
$DIALOG --msgbox "$out\nAttribs: $AnsCkLst\nNote: $AnsRadio" 20 60
done
More samples? Have a look at Using whiptail for choosing USB device and USB removable storage selector: USBKeyChooser
5. Using readline's history
Example:
#!/bin/bash
set -i
HISTFILE=~/.myscript.history
history -c
history -r
myread() {
read -e -p '> ' $1
history -s ${!1}
}
trap 'history -a;exit' 0 1 2 3 6
while myread line;do
case ${line%% *} in
exit ) break ;;
* ) echo "Doing something with '$line'" ;;
esac
done
This will create a file .myscript.history in your $HOME directory, than you could use readline's history commands, like Up, Down, Ctrl+r and others.
echo "Please enter some input: "
read input_variable
echo "You entered: $input_variable"
You can use the built-in read command ; Use the -p option to prompt the user with a question.
Since BASH4, you can now use -i to suggest an answer :
read -e -p "Enter the path to the file: " -i "/usr/local/etc/" FILEPATH
echo $FILEPATH
(But remember to use the "readline" option -e to allow line editing with arrow keys)
If you want a "yes / no" logic, you can do something like this:
read -e -p "
List the content of your home dir ? [Y/n] " YN
[[ $YN == "y" || $YN == "Y" || $YN == "" ]] && ls -la ~/
Bash has select for this purpose. Here's how you would use it in a script:
select result in Yes No Cancel
do
echo $result
done
This is what it would look like to use:
$ bash examplescript.sh
1) Yes
2) No
3) Cancel
#? 1
Yes
#? 2
No
#? 3
Cancel
#?
read -p "Are you alright? (y/n) " RESP
if [ "$RESP" = "y" ]; then
echo "Glad to hear it"
else
echo "You need more bash programming"
fi
inquire () {
echo -n "$1 [y/n]? "
read answer
finish="-1"
while [ "$finish" = '-1' ]
do
finish="1"
if [ "$answer" = '' ];
then
answer=""
else
case $answer in
y | Y | yes | YES ) answer="y";;
n | N | no | NO ) answer="n";;
*) finish="-1";
echo -n 'Invalid response -- please reenter:';
read answer;;
esac
fi
done
}
... other stuff
inquire "Install now?"
...
Here's something I put together:
#!/bin/sh
promptyn () {
while true; do
read -p "$1 " yn
case $yn in
[Yy]* ) return 0;;
[Nn]* ) return 1;;
* ) echo "Please answer yes or no.";;
esac
done
}
if promptyn "is the sky blue?"; then
echo "yes"
else
echo "no"
fi
I'm a beginner, so take this with a grain of salt, but it seems to work.
You want:
Bash builtin commands (i.e. portable)
Check TTY
Default answer
Timeout
Colored question
Snippet
do_xxxx=y # In batch mode => Default is Yes
[[ -t 0 ]] && # If TTY => Prompt the question
read -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx # Store the answer in $do_xxxx
if [[ $do_xxxx =~ ^(y|Y|)$ ]] # Do if 'y' or 'Y' or empty
then
xxxx
fi
Explanations
[[ -t 0 ]] && read ... => Call command read if TTY
read -n 1 => Wait for one character
$'\e[1;32m ... \e[0m ' => Print in green
(green is fine because readable on both white/black backgrounds)
[[ $do_xxxx =~ ^(y|Y|)$ ]] => bash regex
Timeout => Default answer is No
do_xxxx=y
[[ -t 0 ]] && { # Timeout 5 seconds (read -t 5)
read -t 5 -n 1 -p $'\e[1;32m
Do xxxx? (Y/n)\e[0m ' do_xxxx || # read 'fails' on timeout
do_xxxx=n ; } # Timeout => answer No
if [[ $do_xxxx =~ ^(y|Y|)$ ]]
then
xxxx
fi
The easiest way to achieve this with the least number of lines is as follows:
read -p "<Your Friendly Message here> : y/n/cancel" CONDITION;
if [ "$CONDITION" == "y" ]; then
# do something here!
fi
The if is just an example: it is up to you how to handle this variable.
Use the read command:
echo Would you like to install? "(Y or N)"
read x
# now check if $x is "y"
if [ "$x" = "y" ]; then
# do something here!
fi
and then all of the other stuff you need
This solution reads a single character and calls a function on a yes response.
read -p "Are you sure? (y/n) " -n 1
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
do_something
fi
It is possible to handle a locale-aware "Yes / No choice" in a POSIX shell; by using the entries of the LC_MESSAGES locale category, witch provides ready-made RegEx patterns to match an input, and strings for localized Yes No.
#!/usr/bin/env sh
# Getting LC_MESSAGES values into variables
# shellcheck disable=SC2046 # Intended IFS splitting
IFS='
' set -- $(locale LC_MESSAGES)
yesexpr="$1"
noexpr="$2"
yesstr="$3"
nostr="$4"
messages_codeset="$5" # unused here, but kept as documentation
# Display Yes / No ? prompt into locale
echo "$yesstr / $nostr ?"
# Read answer
read -r yn
# Test answer
case "$yn" in
# match only work with the character class from the expression
${yesexpr##^}) echo "answer $yesstr" ;;
${noexpr##^}) echo "answer $nostr" ;;
esac
EDIT:
As #Urhixidur mentioned in his comment:
Unfortunately, POSIX only specifies the first two (yesexpr and noexpr). On Ubuntu 16, yesstr and nostr are empty.
See: https://www.ee.ryerson.ca/~courses/ele709/susv4/xrat/V4_xbd_chap07.html#tag_21_07_03_06
LC_MESSAGES
The yesstr and nostr locale keywords and the YESSTR and NOSTR langinfo items were formerly used to match user affirmative and negative responses. In POSIX.1-2008, the yesexpr, noexpr, YESEXPR, and NOEXPR extended regular expressions have replaced them. Applications should use the general locale-based messaging facilities to issue prompting messages which include sample desired responses.
Alternatively using locales the Bash way:
#!/usr/bin/env bash
IFS=$'\n' read -r -d '' yesexpr noexpr _ < <(locale LC_MESSAGES)
printf -v yes_or_no_regex "(%s)|(%s)" "$yesexpr" "$noexpr"
printf -v prompt $"Please answer Yes (%s) or No (%s): " "$yesexpr" "$noexpr"
declare -- answer=;
until [[ "$answer" =~ $yes_or_no_regex ]]; do
read -rp "$prompt" answer
done
if [[ -n "${BASH_REMATCH[1]}" ]]; then
echo $"You answered: Yes"
else
echo $"No, was your answer."
fi
The answer is matched using locale environment's provided regexps.
To translate the remaining messages, use bash --dump-po-strings scriptname to output the po strings for localization:
#: scriptname:8
msgid "Please answer Yes (%s) or No (%s): "
msgstr ""
#: scriptname:17
msgid "You answered: Yes"
msgstr ""
#: scriptname:19
msgid "No, was your answer."
msgstr ""
To get a nice ncurses-like inputbox use the command dialog like this:
#!/bin/bash
if (dialog --title "Message" --yesno "Want to do something risky?" 6 25)
# message box will have the size 25x6 characters
then
echo "Let's do something risky"
# do something risky
else
echo "Let's stay boring"
fi
The dialog package is installed by default at least with SUSE Linux. Looks like:
In my case I needed to read from a downloaded script i.e.,
curl -Ss https://example.com/installer.sh | sh
The line read -r yn </dev/tty allowed it to read input in this case.
printf "These files will be uploaded. Is this ok? (y/N) "
read -r yn </dev/tty
if [ "$yn" = "y" ]; then
# Yes
else
# No
fi
Single keypress only
Here's a longer, but reusable and modular approach:
Returns 0=yes and 1=no
No pressing enter required - just a single character
Can press enter to accept the default choice
Can disable default choice to force a selection
Works for both zsh and bash.
Defaulting to "no" when pressing enter
Note that the N is capitalsed. Here enter is pressed, accepting the default:
$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]?
Also note, that [y/N]? was automatically appended.
The default "no" is accepted, so nothing is echoed.
Re-prompt until a valid response is given:
$ confirm "Show dangerous command" && echo "rm *"
Show dangerous command [y/N]? X
Show dangerous command [y/N]? y
rm *
Defaulting to "yes" when pressing enter
Note that the Y is capitalised:
$ confirm_yes "Show dangerous command" && echo "rm *"
Show dangerous command [Y/n]?
rm *
Above, I just pressed enter, so the command ran.
No default on enter - require y or n
$ get_yes_keypress "Here you cannot press enter. Do you like this [y/n]? "
Here you cannot press enter. Do you like this [y/n]? k
Here you cannot press enter. Do you like this [y/n]?
Here you cannot press enter. Do you like this [y/n]? n
$ echo $?
1
Here, 1 or false was returned. Note that with this lower-level function you'll need to provide your own [y/n]? prompt.
Code
# Read a single char from /dev/tty, prompting with "$*"
# Note: pressing enter will return a null string. Perhaps a version terminated with X and then remove it in caller?
# See https://unix.stackexchange.com/a/367880/143394 for dealing with multi-byte, etc.
function get_keypress {
local REPLY IFS=
>/dev/tty printf '%s' "$*"
[[ $ZSH_VERSION ]] && read -rk1 # Use -u0 to read from STDIN
# See https://unix.stackexchange.com/q/383197/143394 regarding '\n' -> ''
[[ $BASH_VERSION ]] && </dev/tty read -rn1
printf '%s' "$REPLY"
}
# Get a y/n from the user, return yes=0, no=1 enter=$2
# Prompt using $1.
# If set, return $2 on pressing enter, useful for cancel or defualting
function get_yes_keypress {
local prompt="${1:-Are you sure [y/n]? }"
local enter_return=$2
local REPLY
# [[ ! $prompt ]] && prompt="[y/n]? "
while REPLY=$(get_keypress "$prompt"); do
[[ $REPLY ]] && printf '\n' # $REPLY blank if user presses enter
case "$REPLY" in
Y|y) return 0;;
N|n) return 1;;
'') [[ $enter_return ]] && return "$enter_return"
esac
done
}
# Credit: http://unix.stackexchange.com/a/14444/143394
# Prompt to confirm, defaulting to NO on <enter>
# Usage: confirm "Dangerous. Are you sure?" && rm *
function confirm {
local prompt="${*:-Are you sure} [y/N]? "
get_yes_keypress "$prompt" 1
}
# Prompt to confirm, defaulting to YES on <enter>
function confirm_yes {
local prompt="${*:-Are you sure} [Y/n]? "
get_yes_keypress "$prompt" 0
}
You can use the default REPLY on a read, convert to lowercase and compare to a set of variables with an expression.
The script also supports ja/si/oui
read -rp "Do you want a demo? [y/n/c] "
[[ ${REPLY,,} =~ ^(c|cancel)$ ]] && { echo "Selected Cancel"; exit 1; }
if [[ ${REPLY,,} =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then
echo "Positive"
fi
read -e -p "Enter your choice: " choice
The -e option enables the user to edit the input using arrow keys.
If you want to use a suggestion as input:
read -e -i "yes" -p "Enter your choice: " choice
-i option prints a suggestive input.
Lots of good answers to this question, but from what I can see none of them are my ideal, which would:
Be simple, just a couple lines of shell
Work with a single y/n keypress (no need to press enter)
Default to yes if you just hit enter
Work with an uppercase Y/N as well
Here's my version which does has those properties:
read -n1 -p "Continue? (Y/n) " confirm
if ! echo $confirm | grep '^[Yy]\?$'; then
exit 1
fi
You can modify that conditional to only run on "yes" (just remove the ! in the if statement) or add an else if you want to run code on both branches.
One-liner:
read -p "Continue? [Enter] → Yes, [Ctrl]+[C] → No."
This assumes that "No" and "Cancel" have the same outcome, so no reason to treat them differently.
I noticed that no one posted an answer showing multi-line echo menu for such simple user input so here is my go at it:
#!/bin/bash
function ask_user() {
echo -e "
#~~~~~~~~~~~~#
| 1.) Yes |
| 2.) No |
| 3.) Quit |
#~~~~~~~~~~~~#\n"
read -e -p "Select 1: " choice
if [ "$choice" == "1" ]; then
do_something
elif [ "$choice" == "2" ]; then
do_something_else
elif [ "$choice" == "3" ]; then
clear && exit 0
else
echo "Please select 1, 2, or 3." && sleep 3
clear && ask_user
fi
}
ask_user
This method was posted in the hopes that someone may find it useful and time-saving.
Check this
read -p "Continue? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
Multiple choice version:
ask () { # $1=question $2=options
# set REPLY
# options: x=..|y=..
while $(true); do
printf '%s [%s] ' "$1" "$2"
stty cbreak
REPLY=$(dd if=/dev/tty bs=1 count=1 2> /dev/null)
stty -cbreak
test "$REPLY" != "$(printf '\n')" && printf '\n'
(
IFS='|'
for o in $2; do
if [ "$REPLY" = "${o%%=*}" ]; then
printf '\n'
break
fi
done
) | grep ^ > /dev/null && return
done
}
Example:
$ ask 'continue?' 'y=yes|n=no|m=maybe'
continue? [y=yes|n=no|m=maybe] g
continue? [y=yes|n=no|m=maybe] k
continue? [y=yes|n=no|m=maybe] y
$
It will set REPLY to y (inside the script).
Inspired by the answers of #Mark and #Myrddin I created this function for a universal prompt
uniprompt(){
while true; do
echo -e "$1\c"
read opt
array=($2)
case "${array[#]}" in *"$opt"*) eval "$3=$opt";return 0;; esac
echo -e "$opt is not a correct value\n"
done
}
use it like this:
unipromtp "Select an option: (a)-Do one (x)->Do two (f)->Do three : " "a x f" selection
echo "$selection"
I suggest you use dialog...
Linux Apprentice: Improve Bash Shell Scripts Using Dialog
The dialog command enables the use of window boxes in shell scripts to make their use more interactive.
it's simple and easy to use, there's also a gnome version called gdialog that takes the exact same parameters, but shows it GUI style on X.
more generic would be:
function menu(){
title="Question time"
prompt="Select:"
options=("Yes" "No" "Maybe")
echo "$title"
PS3="$prompt"
select opt in "${options[#]}" "Quit/Cancel"; do
case "$REPLY" in
1 ) echo "You picked $opt which is option $REPLY";;
2 ) echo "You picked $opt which is option $REPLY";;
3 ) echo "You picked $opt which is option $REPLY";;
$(( ${#options[#]}+1 )) ) clear; echo "Goodbye!"; exit;;
*) echo "Invalid option. Try another one.";continue;;
esac
done
return
}
yn() {
if [[ 'y' == `read -s -n 1 -p "[y/n]: " Y; echo $Y` ]];
then eval $1;
else eval $2;
fi }
yn 'echo yes' 'echo no'
yn 'echo absent no function works too!'
One simple way to do this is with xargs -p or gnu parallel --interactive.
I like the behavior of xargs a little better for this because it executes each command immediately after the prompt like other interactive unix commands, rather than collecting the yesses to run at the end. (You can Ctrl-C after you get through the ones you wanted.)
e.g.,
echo *.xml | xargs -p -n 1 -J {} mv {} backup/
As a friend of a one line command I used the following:
while [ -z $prompt ]; do read -p "Continue (y/n)?" choice;case "$choice" in y|Y ) prompt=true; break;; n|N ) exit 0;; esac; done; prompt=;
Written longform, it works like this:
while [ -z $prompt ];
do read -p "Continue (y/n)?" choice;
case "$choice" in
y|Y ) prompt=true; break;;
n|N ) exit 0;;
esac;
done;
prompt=;
I've used the case statement a couple of times in such a scenario, using the case statment is a good way to go about it. A while loop, that ecapsulates the case block, that utilizes a boolean condition can be implemented in order to hold even more control of the program, and fulfill many other requirements. After the all the conditions have been met, a break can be used which will pass control back to the main part of the program. Also, to meet other conditions, of course conditional statements can be added to accompany the control structures: case statement and possible while loop.
Example of using a case statement to fulfill your request
#! /bin/sh
# For potential users of BSD, or other systems who do not
# have a bash binary located in /bin the script will be directed to
# a bourne-shell, e.g. /bin/sh
# NOTE: It would seem best for handling user entry errors or
# exceptions, to put the decision required by the input
# of the prompt in a case statement (case control structure),
echo Would you like us to perform the option: "(Y|N)"
read inPut
case $inPut in
# echoing a command encapsulated by
# backticks (``) executes the command
"Y") echo `Do something crazy`
;;
# depending on the scenario, execute the other option
# or leave as default
"N") echo `execute another option`
;;
esac
exit

Resources