How to "read" in bash without pressing enter - bash

I've done a shell (bash) script that applies predefined rules to files dropped into the terminal.
It works quite well but because it uses 'read' it requires to press Enter once the files are dropped to the term window
This is part of the current code
while true ; do
echo "Drop file(s) here then press [ENTER]:"
echo "( x,q or exit,quit )"
read -p "> " read_file
while read dropped_file ;do
if [ -e ${dropped_file} ] ; then
...bunch of code here...
else
[[ "${dropped_file}" == *[xXqQ]* ]] && exit 1
fi
done <<< $(echo ${read_file} | tr " " "\n")
clear
done
I'd like to omit to press Enter each time I drop files and I was wondering if there is some wizardry to avoid to interact with the keyboard, except when I want to quit the script
Any help would be appreciated
Thanks in advance

EDIT
I've solved with this
while true ; do
read -sn1 read_file
while read splitted_filename; do
dropped_file="${dropped_file}${splitted_filename}"
done <<< $(echo ${read_file})
functionAction
[[ "${dropped_file}" == [xXqQ] ]] && exit 1
done

I think you can use the yes, printf, or the expect command and use the \n key, which should be able to perform what you are looking for.

Related

pipe tailing files into custom filters accepting user input for each line

I'm trying to create custom filters into which I can pipe tailing files. The problem I'm running into is accepting user input to proceed line by line.
I onderstand that my first 'read line' is getting the input from the tailed in file...but I'm struggling with 'read /dev/tty0' grabbing a 'y or n' from the user to proceed.
The problem is my _response variable does not appear to be getting set. Here are my couple of loops:
#!/bin/bash
ECHO_CMD=/bin/echo
READ_CMD=/usr/bin/read
#_input_stream=$0
_input_file=/file.log
tail -f ${_input_file} | {
while IFS= read -r _line
do
lastline="$_line";
echo ${_line} ;
${READ_CMD} -r -p "Are you sure? [y/N] " _response </dev/tty
${ECHO_CMD} "_response=${_response}"
_rtn=`echo ${_response} | grep -e y`
#if [[ $_response =~ ^([yY][eE][sS]|[yY])$ ]]
echo "_rtn= $_rtn "
if [[ ${_rtn} = 0 ]]
then
echo "Continuing"
continue
else
echo "Ending"
break
fi
done
}
exit 1
It is not clear to me which answer was supposed to be the 'continuing' one. Assuming it is y, then try:
#!/bin/bash
_input_file=/file.log
tail -f ${_input_file} | {
while IFS= read -r _line
do
lastline="$_line";
echo ${_line} ;
read -r -p "Are you sure? [y/N] " _response </dev/tty
case "$_response" in
[yY]*)
echo "Continuing"
;;
*)
echo "Ending"
break
;;
esac
done
}
exit 1
Notes
read and echo are bash builtins. Without a clear reason otherwise, they should be used in preference to external binaries.
While an if-then-else statement could be used, the case statement provides an easy way to test variables against globs and execute appropriate commands.
Unlike [[...]], the case statement is POSIX and therefore portable.

How to remove contact from shell script?

I am creating a simple phonebook using unix shell scripts. I have gotten all of my functions to work except the removal of a contact after it has been created. I have tried combining grep and sed in order to accomplish this, but cannot seem to get over the hump. The removal shell i've tried is as follows.
#!/bin/sh
#removeContact.sh
echo “Remove Submenu”
echo “Please input First Name:”
read nameFirst
echo “Please input Last Name:”
read nameLast
x=$(grep -e “$nameFirst” -e “$nameLast” ContactList)
echo $x
sed '/'$x'/ d' ContactList;
echo “$nameFirst $nameLast is removed from your contacts”
exit 0
I'm not sure if I am declaring x incorrectly, or if my syntax is wrong when sed is used.
Any help would be greatly appreciated. Thank you.
#!/bin/bash
ContactList="contacts.txt"
export ContactList
exit=0
while [ $exit -ne 1 ]
do
echo "Main Menu"
echo "(a) Add a Contact"
echo "(r) Remove a Contact"
echo "(s) Search a Contact"
echo "(d) Display All Contact’s Information"
echo "(e) Exit"
echo "Your Choice?"
read choice
if [ "$choice" = "a" ]
then
./addContact.sh
elif [ "$choice" = "r" ]
then
./removeContact.sh
elif [ "$choice" = "s" ]
then
./searchContact.sh
elif [ "$choice" = "d" ]
then
./displayContact.sh
elif [ "$choice" = "e" ]
then
exit=1
else
echo "Error"
sleep 2
fi
done
exit 0
#!/bin/sh
#addContact.sh
ContactList="contacts.txt"
echo “Please input First Name:”
read nameFirst
echo “Please input Last Name:”
read nameLast
echo “Please input Phone Number:”
read number
echo “Please Input Address”
read address
echo “Please input Email:”
read email
echo $nameFirst:$nameLast:$number:$address:$email>> ContactList;
echo "A new contact is added to your book."
exit 0
sed '/'$x'/ d' ContactList
won't remove anything from the file ContactList, it will simply output the changes to standard output.
If you want to edit the file in-place, you'll need the -i flag (easy) or to make a temporary file which is then copied back over ContactList (not so easy, but needed if your sed has no in-place editing option).
In addition, since ContactList is a shell variable referencing the real file contacts.txt, you'll need to use $ContactList.
And, as a final note, since you're using the full line content to do deletion, the presence of an address like 1/15 Station St is going to royally screw up your sed command by virtue of the fact it contains the / character.
I would suggest using awk rather than sed for this task since it's much better suited to field-based data. With the record layout:
$nameFirst:$nameLast:$number:$address:$email
you could remove an entry with something like (including my patented paranoid perfect protection policy):
cp contacts.txt contacts.txt.$(date +%Y.%m.%d.%H.%M.%S_$$)
awk <contacts.txt >tmp.$$ -F: "-vF=$nameFirst" "-vL=$nameLast" '
F != $1 || L != $2 {print}'
mv tmp.$$ contacts.txt

How do I detect input from the user while a bash script is in execution? [duplicate]

I have a shell script that essentially says something like
while true; do
read -r input
if ["$input" = "a"]; then
echo "hello world"
fi
done
That is all well, and good, but I just realized having to hit ENTER presents a serious problem in this situation. What I need is for the script to respond when a key is pressed, without having to hit enter.
Is there a way to achieve this functionality within a shell script?
read -rsn1
Expect only one letter (and don't wait for submitting) and be silent (don't write that letter back).
so the final working snippet is the following:
#!/bin/bash
while true; do
read -rsn1 input
if [ "$input" = "a" ]; then
echo "hello world"
fi
done
Another way of doing it, in a non blocking way(not sure if its what you want). You can use stty to set the min read time to 0.(bit dangerous if stty sane is not used after)
stty -icanon time 0 min 0
Then just run your loop like normal. No need for -r.
while true; do
read input
if ["$input" = "a"]; then
echo "hello world"
fi
done
IMPORTANT!
After you have finished with non blocking you must remember to set stty back to normal using
stty sane
If you dont you will not be able to see anything on the terminal and it will appear to hang.
You will probably want to inlcude a trap for ctrl-C as if the script is quit before you revert stty back to normal you will not be able to see anything you type and it will appear the terminal has frozen.
trap control_c SIGINT
control_c()
{
stty sane
}
P.S Also you may want to put a sleep statement in your script so you dont use up all your CPU as this will just continuously run as fast as it can.
sleep 0.1
P.S.S It appears that the hanging issue was only when i had used -echo as i used to so is probably not needed. Im going to leave it in the answer though as it is still good to reset stty to its default to avoid future problems.
You can use -echo if you dont want what you have typed to appear on screen.
You can use this getkey function:
getkey() {
old_tty_settings=$(stty -g) # Save old settings.
stty -icanon
Keypress=$(head -c1)
stty "$old_tty_settings" # Restore old settings.
}
It temporarily turns off "canonical mode" in the terminal settings
(stty -icanon) then returns the input of "head" (a shell built-in) with the -c1 option which is returning ONE byte of standard input. If you don't include the "stty -icanon" then the script echoes the letter of the key pressed and then waits for RETURN (not what we want). Both "head" and "stty" are shell built-in commands. It is important to save and restore the old terminal settings after the key-press is received.
Then getkey() can be used in combination with a "case / esac" statement for interactive one-key selection from a list of entries:
example:
case $Keypress in
[Rr]*) Command response for "r" key ;;
[Ww]*) Command response for "w" key ;;
[Qq]*) Quit or escape command ;;
esac
This getkey()/case-esac combination can be used to make many shell scripts interactive. I hope this helps.
How to read a single key press into variable c, and print it out. This prints out the key you pressed instantly, withOUT you having to press Enter first:
read -n1 c && printf "%s" "$c"
Or, with a little more "prettifying" in the output print:
read -n1 c && printf "\nYou Pressed: %s\n" "$c"
Example output of the latter command:
$ read -n1 c && printf "\nYou Pressed: %s\n" "$c"
M
You Pressed: M
To suppress your initial keypress from being echoed to the screen, add the -s option as well, which says from the read --help menu:
-s do not echo input coming from a terminal
Here is the final command:
read -sn1 c && printf "You Pressed: %s\n" "$c"
And a demo:
$ read -sn1 c && printf "You Pressed: %s\n" "$c"
You Pressed: p
You can also optionally separate the -sn1 argument into two arguments (-s -n1) for clarity.
References:
I learned about read -n1 from #pacholik here.
See also:
read_keypress.sh in my eRCaGuy_hello_world repo.
I use this bash cmd in C and C++ system calls to read keys here:
[my answer] Capture characters from standard input without waiting for enter to be pressed
[my answer] Read Key pressings in C ex. Arrow keys, Enter key
I have a way to do this in my project: https://sourceforge.net/p/playshell/code/ci/master/tree/source/keys.sh
It reads a single key everytime key_readonce is called. For special keys, a special parsing loop would run to also be able to parse them.
This is the crucial part of it:
if read -rn 1 -d '' "${T[#]}" "${S[#]}" K; then
KEY[0]=$K
if [[ $K == $'\e' ]]; then
if [[ BASH_VERSINFO -ge 4 ]]; then
T=(-t 0.05)
else
T=(-t 1)
fi
if read -rn 1 -d '' "${T[#]}" "${S[#]}" K; then
case "$K" in
\[)
KEY[1]=$K
local -i I=2
while
read -rn 1 -d '' "${T[#]}" "${S[#]}" "KEY[$I]" && \
[[ ${KEY[I]} != [[:upper:]~] ]]
do
(( ++I ))
done
;;
O)
KEY[1]=$K
read -rn 1 -d '' "${T[#]}" 'KEY[2]'
;;
[[:print:]]|$'\t'|$'\e')
KEY[1]=$K
;;
*)
__V1=$K
;;
esac
fi
fi
utils_implode KEY __V0

shell script respond to keypress

I have a shell script that essentially says something like
while true; do
read -r input
if ["$input" = "a"]; then
echo "hello world"
fi
done
That is all well, and good, but I just realized having to hit ENTER presents a serious problem in this situation. What I need is for the script to respond when a key is pressed, without having to hit enter.
Is there a way to achieve this functionality within a shell script?
read -rsn1
Expect only one letter (and don't wait for submitting) and be silent (don't write that letter back).
so the final working snippet is the following:
#!/bin/bash
while true; do
read -rsn1 input
if [ "$input" = "a" ]; then
echo "hello world"
fi
done
Another way of doing it, in a non blocking way(not sure if its what you want). You can use stty to set the min read time to 0.(bit dangerous if stty sane is not used after)
stty -icanon time 0 min 0
Then just run your loop like normal. No need for -r.
while true; do
read input
if ["$input" = "a"]; then
echo "hello world"
fi
done
IMPORTANT!
After you have finished with non blocking you must remember to set stty back to normal using
stty sane
If you dont you will not be able to see anything on the terminal and it will appear to hang.
You will probably want to inlcude a trap for ctrl-C as if the script is quit before you revert stty back to normal you will not be able to see anything you type and it will appear the terminal has frozen.
trap control_c SIGINT
control_c()
{
stty sane
}
P.S Also you may want to put a sleep statement in your script so you dont use up all your CPU as this will just continuously run as fast as it can.
sleep 0.1
P.S.S It appears that the hanging issue was only when i had used -echo as i used to so is probably not needed. Im going to leave it in the answer though as it is still good to reset stty to its default to avoid future problems.
You can use -echo if you dont want what you have typed to appear on screen.
You can use this getkey function:
getkey() {
old_tty_settings=$(stty -g) # Save old settings.
stty -icanon
Keypress=$(head -c1)
stty "$old_tty_settings" # Restore old settings.
}
It temporarily turns off "canonical mode" in the terminal settings
(stty -icanon) then returns the input of "head" (a shell built-in) with the -c1 option which is returning ONE byte of standard input. If you don't include the "stty -icanon" then the script echoes the letter of the key pressed and then waits for RETURN (not what we want). Both "head" and "stty" are shell built-in commands. It is important to save and restore the old terminal settings after the key-press is received.
Then getkey() can be used in combination with a "case / esac" statement for interactive one-key selection from a list of entries:
example:
case $Keypress in
[Rr]*) Command response for "r" key ;;
[Ww]*) Command response for "w" key ;;
[Qq]*) Quit or escape command ;;
esac
This getkey()/case-esac combination can be used to make many shell scripts interactive. I hope this helps.
How to read a single key press into variable c, and print it out. This prints out the key you pressed instantly, withOUT you having to press Enter first:
read -n1 c && printf "%s" "$c"
Or, with a little more "prettifying" in the output print:
read -n1 c && printf "\nYou Pressed: %s\n" "$c"
Example output of the latter command:
$ read -n1 c && printf "\nYou Pressed: %s\n" "$c"
M
You Pressed: M
To suppress your initial keypress from being echoed to the screen, add the -s option as well, which says from the read --help menu:
-s do not echo input coming from a terminal
Here is the final command:
read -sn1 c && printf "You Pressed: %s\n" "$c"
And a demo:
$ read -sn1 c && printf "You Pressed: %s\n" "$c"
You Pressed: p
You can also optionally separate the -sn1 argument into two arguments (-s -n1) for clarity.
References:
I learned about read -n1 from #pacholik here.
See also:
read_keypress.sh in my eRCaGuy_hello_world repo.
I use this bash cmd in C and C++ system calls to read keys here:
[my answer] Capture characters from standard input without waiting for enter to be pressed
[my answer] Read Key pressings in C ex. Arrow keys, Enter key
I have a way to do this in my project: https://sourceforge.net/p/playshell/code/ci/master/tree/source/keys.sh
It reads a single key everytime key_readonce is called. For special keys, a special parsing loop would run to also be able to parse them.
This is the crucial part of it:
if read -rn 1 -d '' "${T[#]}" "${S[#]}" K; then
KEY[0]=$K
if [[ $K == $'\e' ]]; then
if [[ BASH_VERSINFO -ge 4 ]]; then
T=(-t 0.05)
else
T=(-t 1)
fi
if read -rn 1 -d '' "${T[#]}" "${S[#]}" K; then
case "$K" in
\[)
KEY[1]=$K
local -i I=2
while
read -rn 1 -d '' "${T[#]}" "${S[#]}" "KEY[$I]" && \
[[ ${KEY[I]} != [[:upper:]~] ]]
do
(( ++I ))
done
;;
O)
KEY[1]=$K
read -rn 1 -d '' "${T[#]}" 'KEY[2]'
;;
[[:print:]]|$'\t'|$'\e')
KEY[1]=$K
;;
*)
__V1=$K
;;
esac
fi
fi
utils_implode KEY __V0

Bash Shell Scripting - detect the Enter key

I need to compare my input with Enter/Return key...
read -n1 key
if [ $key == "\n" ]
echo "###"
fi
But this is not working.. What is wrong with this code
Several issues with the posted code. Inline comments detail what to fix:
#!/bin/bash
# ^^ Bash, not sh, must be used for read options
read -s -n 1 key # -s: do not echo input character. -n 1: read only 1 character (separate with space)
# double brackets to test, single equals sign, empty string for just 'enter' in this case...
# if [[ ... ]] is followed by semicolon and 'then' keyword
if [[ $key = "" ]]; then
echo 'You pressed enter!'
else
echo "You pressed '$key'"
fi
Also it is good idea to define empty $IFS (internal field separator) before making comparisons, because otherwise you can end up with " " and "\n" being equal.
So the code should look like this:
# for distinguishing " ", "\t" from "\n"
IFS=
read -n 1 key
if [ "$key" = "" ]; then
echo "This was really Enter, not space, tab or something else"
fi
I'm adding below code just for reference if someone will want to use such solution containing countdown loop.
IFS=''
echo -e "Press [ENTER] to start Configuration..."
for (( i=10; i>0; i--)); do
printf "\rStarting in $i seconds..."
read -s -N 1 -t 1 key
if [ "$key" = $'\e' ]; then
echo -e "\n [ESC] Pressed"
break
elif [ "$key" == $'\x0a' ] ;then
echo -e "\n [Enter] Pressed"
break
fi
done
read reads a line from standard input, up to but not including the new line at the end of the line. -n specifies the maximum number of characters, forcing read to return early if you reach that number of characters. It will still end earlier however, when the Return key is pressed. In this case, its returning an empty string - everything up to but not including the Return key.
You need to compare against the empty string to tell if the user immediately pressed Return.
read -n1 KEY
if [[ "$KEY" == "" ]]
then
echo "###";
fi
None of these conditions worked for me and so I've came up with this one:
${key} = $'\0A'
Tested on CentOS with Bash 4.2.46.

Resources