Korn Shell - How can I make “Press any key to continue” - shell

I am writing korn shell script. I need to put funtionality called "Press any key to continue" to execute rest of the script.How can I achieve this?
Thanks in advance

You can use read command:
read -n1 -r -p "Press space to continue..." key
if [ "$key" = '' ]; then
# Space pressed, do something
# echo [$key] is empty when SPACE is pressed # uncomment to trace
else
# Anything else pressed, do whatever else.
# echo [$key] not empty
fi
This is very simple script.
#!/usr/bin/ksh
echo "First Method"
read -s -n 1 -p "Press any key to continue..."
# insert echo here for cleaner output
echo
echo "Second Method"
echo "Press any key to continue..."
read -s -n 1 any_key
echo "Now exiting"
exit 0
Information about the read command:
To get input from the keyboard, you use the read command. The read command takes input from the keyboard and assigns it to a variable. Here is an example:
echo -n "Enter some text > "
read text
echo "You entered: $text"

read is the command for input (usually form terminal). There is (nearly) no way around the <Enter>, its the way to tell read to start processing the input.
If you want to start processing after whatever keystroke, you have to dive deep into terminal settings via stty, changing to raw mode and .....
And it is likely your terminal is in foul mood when you are coming out of that.
The effort to avoid side effects is usually not worth the effect.
Think about, you changed the terminal mode, waiting for a keystroke, do you allow Ctrl-C, backspace, ESC, ... to be keystroke or do they need special handling? You changed the terminal mode, you are respnsible now. How to set the terminal to a sane state if the process has been killed , ....
If you are up for a big challenge, go for it :-)

OK, I understand why my post was deleted. Here I go again.
What I did is to create a function 'readOne' and call it when needed.
readOne () {
tput smso
echo "Press any key to return \c"
tput rmso
oldstty=`stty -g`
stty -icanon -echo min 1 time 0
dd bs=1 count=1 >/dev/null 2>&1
stty "$oldstty"
echo
}
Here is the explanation from the author :
The tty driver controls how input lines that you type are delivered to programs. Normally a tty driver will wait until a complete line is available. It also handles stuff like backspace so that program doesn'y need to. The stty command lets you change the way the tty driver works. "stty -a" will display all of the settings. You should do that to see what's available.
"stty -g" displays all of the settings too. But it's encoded and you can't understand the output. But you can save the output and feed it back into the stty command. So:
oldtty='stty -g'
stty $oldstty
will save and restore the original settings of the tty driver.
stty -icanon -echo min 1 time 0
is setting some options in the tty driver. -icanon turns off all special character processing. Now a backspace will be passed to the program rather than being processed. And a carriage return won't terminate a line. So now min and time control when a read has finished. "min 1" says we need at least one character. "time 0" means that we won't wait a while before completing a read. So each read from the program may return after just one character.
the -echo just turns off echo. I would not have done that.
dd is a program that is prepared to read data that is not organized into lines. This dd will read one block (count=1) of data. And that block will be one character in length (size=1). So the dd will read one character and return to the script.
The final echo moves the cursor to the next line.

Related

Bash: Is there a way to require the enter key to be pushed with read -n once character limit is reached?

Is there a way to require the enter key to be pushed with read -n once character limit is reached, rather than automatically jumping to the following line in the script?
For example, in the following script:
echo "Please enter your credentials below"
read -p "Please enter your username: " -e -n 15 usern
read -p "Please enter your password: " -s -e -n 15 passw
Once 15 chars are entered for the username, it automatically jumps to the password prompt. However, it would be more user-friendly to simply stop allowing input once the 15 chars are reached (besides the backspace key and enter key), and require the user to press enter to continue to the password prompt. This is how most logins work, after all...
I'm aware that I could use a while loop/if statement restricting the char limit in the usern variable (e.g. with -gt), but I was wondering how I could specifically limit the user to pressing either backspace or enter (or even the arrow keys if they want to edit a single character from their username, but I'm not worried about this right now), once those 15 characters are reached, and REQUIRE the user to press enter to continue to the following prompt.
Hopefully what I am asking for makes sense. Thanks to everyone in advance!
As mentioned in the comments, bash's read isn't able to reproduce the expected behaviour for specific keys. However, you can avoid using read by rolling your own input parsing.
Some points to take into account:
Terminal line settings need to be adjusted with stty, to avoid printing input characters as they are typed (i.e. we only want to print our messages, otherwise characters would be duplicated);
Terminal emulator needs to support VT100 escape codes, in order to erase the line and move the cursor when redrawing the prompt;
Characters can be compared by ascii code, but when enter is pressed, we don't get a read character for the newline itself. However, since other whitespace characters are read, this is not an issue.
The following script implements the expected behaviour, by accepting characters up to a given limit (for the password, no typed characters are printed). The input is only submitted when enter is pressed.
#!/bin/sh
set -eu
parse() {
message=$1
var=$2
is_password=$3
input=
stty -icanon -echo
while true; do
# Clear line, move cursor to beginning, then print prompt
if [ "$is_password" -eq 1 ]; then
printf '\33[2K\r'"$message "
else
printf '\33[2K\r'"$message $input"
fi
# Read 1 character
i=$(dd bs=1 count=1 2>/dev/null)
# Was a backspace read?
if echo "$i" | grep -qP '\x7f'; then
# Remove last character
input=$(echo "$input" | sed 's/.$//')
# Only add read character if input field limit wasn't reached
elif [ ${#input} -lt 15 ]; then
input=$input$i
fi
# Was a newline read?
if [ -z "$i" ]; then
break
fi
done
stty icanon echo
eval "$var=$input"
}
echo "Please enter your credentials below"
parse "Please enter your username:" "usern" 0
printf "\n%s\n" "Read username: $usern"
parse "Please enter your password:" "userp" 1
printf "\n%s\n" "Read password: $userp"

In bash print to line above terminal output

EDIT: Corrected process/thread terminology
My shell script has a foreground process that reads user input and a background process that prints messages. I would like to print these messages on the line above the input prompt rather than interrupting the input. Here's a canned example:
sleep 5 && echo -e "\nINFO: Helpful Status Update!" &
echo -n "> "
read input
When I execute it and type "input" a bunch of times, I get something like this:
> input input input inp
INFO: Helpful Status Update!
ut input
But I would like to see something like this:
INFO: Helpful Status Update!
> input input input input input
The solution need not be portable (I'm using bash on linux), though I would like to avoid ncurses if possible.
EDIT: According to #Nick, previous lines are inaccessible for historical reasons. However, my situation only requires modifying the current line. Here's a proof of concept:
# Make named pipe
mkfifo pipe
# Spawn background process
while true; do
sleep 2
echo -en "\033[1K\rINFO: Helpful Status Update!\n> `cat pipe`"
done &
# Start foreground user input
echo -n "> "
pid=-1
collected=""
IFS=""
while true; do
read -n 1 c
collected="$collected$c"
# Named pipes block writes, so must do background process
echo -n "$collected" >> pipe &
# Kill last loop's (potentially) still-blocking pipe write
if kill -0 $pid &> /dev/null; then
kill $pid &> /dev/null
fi
pid=$!
done
This produces mostly the correct behavior, but lacks CLI niceties like backspace and arrow navigation. These could be hacked in, but I'm still having trouble believing that a standard approach hasn't already been developed.
The original ANSI codes still work in bash terminal on Linux (and MacOS), so you can use \033[F where \033 is the ESCape character. You can generate this in bash terminal by control-V followed by the ESCape character. You should see ^[ appear. Then type [F. If you test the following script:
echo "original line 1"
echo "^[[Fupdated line 1"
echo "line 2"
echo "line 3"
You should see output:
updated line 1
line 2
line 3
EDIT:
I forgot to add that using this in your script will cause the cursor to return to the beginning of the line, so further input will overwrite what you have typed already. You could use control-R on the keyboard to cause bash to re-type the current line and return the cursor to the end of the line.

Bash read command - how to accept control characters

I am using the following to read a bunch of parameters from the user.
read -p "Name`echo $'\n> '`" NAME
All my parameters have default values. I want to provide an option to the user to skip providing values at any point. i.e. user might provide values for first 3 parameters and press Ctrl+s to skip entering values for the rest of them.
How can I trap Ctrl+s?
Ctrl+S is the terminal scroll lock character, and is not immediately available. You have two options:
Work with the system, use the system standard key combos, and make life easier for yourself and everyone else.
Fight the system, insist on using this key combo, do anything it takes to make it happen, and then live with the consequences.
If you want to work with the system, consider using a blank line and/or Ctrl+D, which is already extensively used to end input. This is easy and robust:
if read -p "Name (or blank for done)"$'\n> ' name && [[ $name ]]
then
echo "User entered $name"
else
echo "User is done entering things"
fi
Alternatively, here's a start for fighting the system:
#!/bin/bash
settings=$(stty -g)
echo "Enter name or ctrl+s to stop:"
stty stop undef # or 'stty raw' to read other control chars
str=""
while IFS= read -r -n 1 c && [[ $c ]]
do
[[ $c = $'\x13' ]] && echo "Ctrl+S pressed" && break
str+="$c"
done
echo "Input was $str"
stty "$settings"
This will correctly end when the user hits Ctrl+S on a standard configuration, but since it doesn't work with the system, it needs additional work to support proper line editing and less common configurations.
Not sure what you mean by trapping. The user can input Ctrl+S by typing Ctrl+V and then Ctrl+S, and your script can then do the check:
if [[ $NAME == '^S' ]]; then
...
# skip asking for more values and continue with default values
fi

Bash Unix: How can i pause a process without using sleep command

Hi im trying to pause the execution of a process so that a user cannot make multiple entries until 5 seconds have elapsed. I tried using sleep but sleep simply stops and then executes all the inputs the user ran while the process was asleep, i dont want there to be any input read from when the program was sleeping.
example of what i want: a chat bot
input 1: hi
output: "reply from program"
input 2 (before 5 seconds is up): "whats new"
-no output-
input 3 (5 seconds have passed): "how are you"
output: im fine.
example of what sleep command does:
input 1. "hi"
output "hey"
input 2 (before 5 seconds): "whats new"
-no output- waits
input 3: "how are you"
output: "not much is new"
output: "im fine"
You can read and throw away the user's input after the 5 seconds is up
# 1.
read -p "prompt 1: " first_thing
sleep 4
# this reads and ignores whatever the user has typed during the sleep
while read -t 1 _garbage; do :; done
# 2.
read -p "prompt 2: " next_thing
reads -t 1 option is a timeout of one second if there's nothing to read.
Testing
with input
$ read -p "prompt 1: " first_thing; sleep 4; while read -t 1 _garbage; do :;done ; read -p "prompt 2: " next_thing
prompt 1: foo
bar
baz
prompt 2: qux
$ echo $first_thing $next_thing
foo qux
no "extra" input before the 2nd read -- process does not "hang" awaiting input
$ read -p "prompt 1: " first_thing; sleep 4; while read -t 1 _garbage; do :;done ; read -p "prompt 2: " next_thing
prompt 1: hello
prompt 2: world
$ echo $first_thing $next_thing
hello world
This is by no means easy, and you need to be more specific about how you expect the script to respond to keyboard input.
There is no way to "lock" the keyboard; the user can continue punching keys as they see fit. If you are content for the keys to be echoed, but you want the input to be ignored, you could do something like the following:
# Beware! Read the entire answer; don't just use this command
timeout 5 bash -c 'while :;do read -s -d ""; done'
The timeout utility runs a command, killing it when the specified number of seconds have elapsed; the specified number may be a decimal fraction. The loop around the read command is necessary because the read would other terminate as soon as the Enter key is pressed; while :; do is a standard idiom for "loop forever".
The timeout command is part of Gnu coreutils. If you don't have it, perhaps because you are using a BSD derivative, you can probably find alternatives. There is a FreeBSD command, probably available on other BSDs including Mac OS X, called timelimit; I believe the correct invocation would be to replace timeout 5 with timelimit -t 5 -s9, but I don't have any easy way of testing.
You need to get the read command to actually read input immediately, as opposed to waiting until the Enter key is pressed. Otherwise, the typed input will still be available to the next command after the read is terminated.
There are several ways to do this. One is to use the -n 1 flag to cause the read to return after each character; another one is to use -d "" to set the end of input character to NUL, which has the side effect of putting read into character-at-a-time mode.
Also, you will probably want to suppress echo of the keys pressed while you are in the read loop. You can do that by adding the -s flag to the read command, but again that will have the side effect of leaving the terminal in "no echo" mode when the read command is interrupted. [Note 1]
Unfortunately, you'll probably find that the terminal settings have been permanently changed, because when read is killed by the timeout command, it doesn't have a chance to restore the terminal settings. So you'll end up with a terminal which doesn't echo, doesn't handle backspace and other line-editing commands, and doesn't honor Ctrl-D, amongst other issues.
To avoid this problem, you need to save and restore the terminal settings. You can do that with the stty command, as follows:
# Save the terminal settings
saved=$(stty -g)
# Ignore input for 5 seconds, suppressing echo
timeout 5 bash -c 'while :;do read -s -d ""; done'
# Restore the terminal settings
stty "$saved"
If you don't suppress echo, you'll find that your input prompt may appear on the same line as the ignored input. You could avoid that by outputting a "carriage return / erase to end of line" control sequence before the prompt:
tput cr; tput el; read -p "Give me some input: "
The answer of #glenn is a good answer!
Maybe you don't know in front how long you are going to sleep (you are calling
some other functions).
When you just want to flush the input queue, you can try a similar approach:
echo "What is your name?"
read name
echo "Please be quiet for 5 seconds"
sleep 5
smalltime=0.000001
read -t ${smalltime} -s garbage
while [ -n "${garbage}" ]; do
echo "Flushing $garbage"
read -t ${smalltime} -s garbage
done
echo "Hello ${name}, please say something else"
read y
echo "You said $y"

How can I pause in zsh?

I want to write this bash loop for zsh
while true; do echo "print something"; read -p "pause"; done
This loop echos, then waits for the user to press enter. If I enter it as is, the read statement doesn't pause, causing zsh to infinitely echo "print something" without waiting for the user to press enter.
In zsh:
read -s -k '?Press any key to continue.'
From man zshbuiltins:
-s Don't echo back characters if reading from the terminal.
-k Read only one character.
name?prompt Name is omitted, thus user input is stored in the REPLY variable (and we ignore it). The first argument contains a ?, thus the remainder of this word is used as a prompt on standard error when the shell is interactive.
To include a newline after the prompt:
read -s -k $'?Press any key to continue.\n'
$'' is explained under QUOTING in man zshmisc.
Finally, a pause function that takes an arbitrary prompt message in a script that does what the OP asks:
#!/usr/bin/env zsh
pause() read -s -k "?$*"$'\n'
while true; do
echo "print something"
pause "pause"
done
Since this is about the only search result I could find, and I found it helpful but still a bit confusing, here is another way of putting it: If all you want to do is echo a line of text and wait for the user to press enter ...
read \?"I am waiting for you to press [Enter] before I continue."
It looks like -p does something different in zsh. You will probably need something like read some_variable\?pause.
#!/bin/zsh
pause()
{
echo "$*"; read -k1 -s
}
now we can call the function with any prompt text:
pause "paused! press any key to continue"
pause "you can write anything here :)"
If you want a way that works in both bash and zsh, and ensures I/O to/from the terminal:
# Prompt for a keypress to continue. Customise prompt with $*
function pause {
>/dev/tty printf '%s' "${*:-Press any key to continue... }"
[[ $ZSH_VERSION ]] && read -krs # Use -u0 to read from STDIN
[[ $BASH_VERSION ]] && </dev/tty read -rsn1
printf '\n'
}
export_function pause

Resources