How to 'read -s' in shell? - linux

I know that user input can be read silently using bash with read -s someVar and I was wondering if there is a /bin/sh equivalent that allows user input without displaying it on the command line?
Note: I am just curious if /bin/sh read supports this feature somehow.

Use the stty command to turn off echoing of typed characters.
get_entry () {
printf "Choose: "
stty -echo
IFS= read -r choice
stty echo
printf '\n'
}
get_entry
printf "You chose %s\n" "$choice"

Related

'read -r' doesn't read beyond first line in a loop that does ssh [duplicate]

I have the following shell script. The purpose is to loop thru each line of the target file (whose path is the input parameter to the script) and do work against each line. Now, it seems only work with the very first line in the target file and stops after that line got processed. Is there anything wrong with my script?
#!/bin/bash
# SCRIPT: do.sh
# PURPOSE: loop thru the targets
FILENAME=$1
count=0
echo "proceed with $FILENAME"
while read LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done < $FILENAME
echo "\ntotal $count targets"
In do_work.sh, I run a couple of ssh commands.
The problem is that do_work.sh runs ssh commands and by default ssh reads from stdin which is your input file. As a result, you only see the first line processed, because the command consumes the rest of the file and your while loop terminates.
This happens not just for ssh, but for any command that reads stdin, including mplayer, ffmpeg, HandBrakeCLI, httpie, brew install, and more.
To prevent this, pass the -n option to your ssh command to make it read from /dev/null instead of stdin. Other commands have similar flags, or you can universally use < /dev/null.
A very simple and robust workaround is to change the file descriptor from which the read command receives input.
This is accomplished by two modifications: the -u argument to read, and the redirection operator for < $FILENAME.
In BASH, the default file descriptor values (i.e. values for -u in read) are:
0 = stdin
1 = stdout
2 = stderr
So just choose some other unused file descriptor, like 9 just for fun.
Thus, the following would be the workaround:
while read -u 9 LINE; do
let count++
echo "$count $LINE"
sh ./do_work.sh $LINE
done 9< $FILENAME
Notice the two modifications:
read becomes read -u 9
< $FILENAME becomes 9< $FILENAME
As a best practice, I do this for all while loops I write in BASH.
If you have nested loops using read, use a different file descriptor for each one (9,8,7,...).
More generally, a workaround which isn't specific to ssh is to redirect standard input for any command which might otherwise consume the while loop's input.
while read -r line; do
((count++))
echo "$count $line"
sh ./do_work.sh "$line" </dev/null
done < "$filename"
The addition of </dev/null is the crucial point here, though the corrected quoting is also somewhat important for robustness; see also When to wrap quotes around a shell variable?. You will want to use read -r unless you specifically require the slightly odd legacy behavior you get for backslashes in the input without -r. Finally, avoid upper case for your private variables.
Another workaround of sorts which is somewhat specific to ssh is to make sure any ssh command has its standard input tied up, e.g. by changing
ssh otherhost some commands here
to instead read the commands from a here document, which conveniently (for this particular scenario) ties up the standard input of ssh for the commands:
ssh otherhost <<'____HERE'
some commands here
____HERE
ssh -n option prevents checking the exit status of ssh when using HEREdoc while piping output to another program.
So use of /dev/null as stdin is preferred.
#!/bin/bash
while read ONELINE ; do
ssh ubuntu#host_xyz </dev/null <<EOF 2>&1 | filter_pgm
echo "Hi, $ONELINE. You come here often?"
process_response_pgm
EOF
if [ ${PIPESTATUS[0]} -ne 0 ] ; then
echo "aborting loop"
exit ${PIPESTATUS[0]}
fi
done << input_list.txt
This was happening to me because I had set -e and a grep in a loop was returning with no output (which gives a non-zero error code).

In Bash, how can I output a new line after read -es?

current code:
echo -n "password: "
read -es password
echo -n "ok"
behavoir in linux:
password: ok
Can we make it like:
password:
ok
thanks,
wxie
The following will work:
read -p "password: " -es password
echo
echo "ok"
Maybe:
read -p "password: " -es password
printf "\n ok \n"
If you want a newline before the OK, this should do it:
echo -e "\nok"
The -e enables interpretation of the backslash codes.
From echo's man page: -n do not output the trailing newline. Try it without the option.
We can use \n as a line separator in printf, there is an extra line for padding after "ok" which you can remove
printf "password: " && read -es password && printf "\n\nok\n"
If you are saving a password this way it would still be contained in the variable $password in plain text, the string below would print it openly.
printf "$password"
Your code sample uses echo and should be using printf, there are articles on echo's POSIX compliance, echo could work like Greg Tarsa points out, but not all versions are built with switch options like -e or -n.
I find it readable to use printf, also other "languages" have similar commands such as PRINT, print, println, print() and others.
If you need to use echo you could
echo -n "password: " && read -es password && echo -e "\nok\n"

Can you portably read sensitive input from the commandline?

The bash builtin read has a flag -s that prevents it from echoing whatever is being read from the commandline. After searching opengroup.org and filtering through all the other meanings for read, I still haven't found a POSIX/portable equivalent. Is there a reasonable way to do this?
In bash it's easy enough:
$ bash -c 'read -sp "What is your password? " password; printf "\n%s\n" "$password"'
What is your password?
I'll never tell!
But in sh…
$ dash -c 'printf "What is your password? "; read password >/dev/null 2>&1; printf "\n%s\n" "$password"'
What is your password? I'll never tell!
I'll never tell!
So the answer to your question is as described in this link
you can turn off by using builtin command stty
stty -echo
ps:
dont forget to save your previous settings
old_set=$(stty -g)
stty -echo
read -r password
stty "$old_set"

How to read just a single character in shell script

I want similar option like getche() in C. How can I read just a single character input from command line?
Using read command can we do it?
In bash, read can do it:
read -n1 ans
read -n1 works for bash
The stty raw mode prevents ctrl-c from working and can get you stuck in an input loop with no way out. Also the man page says stty -raw is not guaranteed to return your terminal to the same state.
So, building on dtmilano's answer using stty -icanon -echo avoids those issues.
#/bin/ksh
## /bin/{ksh,sh,zsh,...}
# read_char var
read_char() {
stty -icanon -echo
eval "$1=\$(dd bs=1 count=1 2>/dev/null)"
stty icanon echo
}
read_char char
echo "got $char"
In ksh you can basically do:
stty raw
REPLY=$(dd bs=1 count=1 2> /dev/null)
stty -raw
read -n1
reads exactly one character from input
echo "$REPLY"
prints the result on the screen
doc: https://www.computerhope.com/unix/bash/read.htm
Some people mean with "input from command line" an argument given to the command instead reading from STDIN... so please don't shoot me. But i have a (maybe not most sophisticated) solution for STDIN, too!
When using bash and having the data in a variable you can use parameter expansion
${parameter:offset:length}
and of course you can perform that on given args ($1, $2, $3, etc.)
Script
#!/usr/bin/env bash
testdata1="1234"
testdata2="abcd"
echo ${testdata1:0:1}
echo ${testdata2:0:1}
echo ${1:0:1} # argument #1 from command line
Execution
$ ./test.sh foo
1
a
f
reading from STDIN
Script
#!/usr/bin/env bash
echo please type in your message:
read message
echo 1st char: ${message:0:1}
Execution
$ ./test.sh
please type in your message:
Foo
1st char: F

read not prompting when i/p redirected from a file

I have this:
while read -r line; do echo "hello $line"; read -p "Press any key" -n 1; done < file
hello This is line 1
hello his is line 2
hello his is line 3
hello his is line 4
hello his is line 5
hello his is line 6
hello his is line 7
Why do I not see the prompt "Press any key" ?
Quote from man bash:
-p prompt
Display prompt on standard error, without a trailing new
line, before attempting to read any input. The prompt is
displayed only if input is coming from a terminal.
So, because you read lines from file but not from terminal prompt not displayed.
As others mentioned, you don't see the prompt because bash only prints the prompt when stdin is a terminal. In your case, stdin is a file.
But there's a bigger bug here: It seems to me that you want to read from two places: a file and the user. You'll have to do some redirection magic to accomplish this:
# back up stdin
exec 3<&0
# read each line of a file. the IFS="" prevents read from
# stripping leading and trailing whitespace in the line
while IFS="" read -r line; do
# use printf instead of echo because ${line} might have
# backslashes in it which some versions of echo treat
# specially
printf '%s\n' "hello ${line}"
# prompt the user by reading from the original stdin
read -p "Press any key" -n 1 <&3
done <file
# done with the stdin backup, so close the file descriptor
exec 3<&-
Note that the above code won't work with /bin/sh because it's not POSIX compliant. You'll have to use bash. I'd recommend making it POSIX compliant by changing the line that prompts the user:
printf 'Press enter to continue' >&2
read <&3
You may explicitly read from the controlling terminal /dev/tty:
while IFS="" read -r line; do
echo "hello $line"
read -p "Press any key" -n 1 </dev/tty
done < file

Resources