How to modify one’s input in a bash interactive script? [duplicate] - bash

Consider the following minimised Bash Script:
echo Enter your name:
read NAME
echo $NAME
Now if I run the script and enter a name and want to navigate through my input with my arrow keys, [[D^ characters are getting returned.
How would you rewrite that script to cater for such behaviour, i.e. let me navigate with keys instead of winning an ASCII contest ?

These character sequences are the way that the terminal communicates that "cursor left" has been pressed. If the program receiving it does not interpret it as such and instead just displays them (after filtering the escape character), that's what you get.
Luckily for you, the read command of bash has the option -e to enable use of Readline for reading in the line. Readline performs all that handling (as it does on the normal bash command input).

Thanks to Andreas and some due dilligence with a search engine, I was able to rewrite my script:
echo Enter your name:
read -e NAME
echo $NAME
Now navigating through the input with arrow keys, works like a expected.
Here you can learn more about the read builtin command.

Related

How to present editable input to a user with BASH and ZSH?

I need to accept user input within a shell script that can run both in BASH and ZSH. I'm accustomed to using readline in other languages, but this doesn't seem to be a viable option in shell scripting.
An example prompt might be:
Please enter the value> 1234_
How can I present a user with editable input that has a default value that can be edited (backspaced) that's compatible with both shells?
User #cyrus mentioned in the comments, but is better directly as an answer instead:
read -e -p 'Please enter the value> ' -i '1234' myvar
echo $myvar
results in an editable default of 1234
Please enter the value> 1234
1234
If I did not misunderstand ...
In terms of "Backspace" functionality, to erase the last characters and add some new ..
This works in both shells with a simple "read" in the script.
echo -n "Enter value > "
read v
echo $v
If you have other issues, could you kindly rephrase to make your question clearer ?
What do you use (Example Code).
What do you perform exactly in terms of editing (only BS or do you mean line editing commands CTRL-A, etc)
What is the result / error, what you want to get fixed.
Thanks.

Bash script user input prompt

I am having issues with my known methods of generating user input prompts:
read -p "Input something: " variabile
This causes issues if attempting to use the arrow keys, it echoes the ANSI code for each arrow key stroke
read -e -p "Input something: " variable
This fixes the arrow keys issue but when reaching the width of the terminal, text input doesn't continue on a newline but on the same line, overwriting (visually) the existing input
echo -n "Input something: "; read -e variable
This apparently fixes both formerly described issues... until I found that typing something then hitting backspace overwrites the prompt and also when the input is longer, from the second newline of input the visual overwriting manifests again.
So is there a good way of producing prompts without the above issues?
UPDATE
After re-checking, I now know what's causing the input overwrite for read -e -p
I am using these variables for highlighting text for the read prompt:
highlight=$(echo -e "\e[1;97m")
clear=$(echo -e "\e[0m")
read -e -p "Input$highlight something$clear: " variable
This is the only way I could make the highlighting work inside read prompt (assigning escape sequences to the variables doesn't work, I need to echo them like I did) but they also seem to cause the input overwrite issue.
As dimo414 mentions, readline thinks the prompt is longer than it is. It counts every character in the terminal escape sequence in computing the length. You can see how long it thinks the escape sequence is as follows
echo ${#highlight}
In the bash PS1 prompt, surrounding such an escape sequence with "\[" and "\]" instructs readline to ignore everything between when calculating current line length, but these are not the right escapes for the bash read built-in.
The escapes for read are $'\001' and $'\002', as mentioned in BashFAQ, but in my experience, you need the -e option on read, as well. The brute force way to do what you want would be:
read -e -p "Input "$'\001'"${highlight}"$'\002'something$'\001'"${clear}"$'\002'": "
You should use tput rather than hard-coded escape sequences, for the sake of terminal independence. Read man 5 termcap.
See my dotfiles for elegant bash functions to do the begin/end quoting above for you.
The shell keeps track of how long it thinks the prompt is, in order to know where the user's input starts and stops. Unfortunately when you print color escape codes in a prompt you throw of Bash's counting, since it expects the escape characters to take up space in the terminal.
To avoid that, you just need to wrap all color sequences in \[ and \], which tells your shell the enclosed characters are non-printing, and should not be counted.
For example, your highlight variable should be:
highlight=$(echo -e "\[\e[1;97m\]")
Personally, I use the color and pcolor functions from my Prompt.gem project, which handles the proper escaping and would make your command much easier to read:
read -e -p "Input $(pcolor DEFAULT BOLD)something$(pcolor): " variable

Bash- passing input without shell interpreting parameter expansion chars

So I have a script where I type the script.sh followed by input for a set of if-else statements. Like this:
script.sh fnSw38h$?2
The output echoes out the input in the end.
But I noticed that $? is interpreted as 0/1 so the output would echo:
fnSw38h12
How can I stop the shell from expanding the characters and take it face value?
I looked at something like opt noglob or something similar but they didn't work.
When I put it like this:
script.sh 'fnSw38h$?2'
it works. But how do I capture that within single quotes ('') when I can't state variables inside it like Var='$1'
Please help!
How to pass a password to a script
I gather from the comments that the true purpose of this script is to validate a password. If this is an important or sensitive application, you really should be using professional security tools. If this application is not sensitive or this is just a learning exercise, then read on for a first introduction to the issues.
First, do not do this:
script.sh fnSw38h$?2
This password will appear in ps and be visible to any user on the system in plain text.
Instead, have the user type the password as input to the script, such as:
#!/bin/sh
IFS= read -r var
Here, read will gather input from the keyboard free from shell interference and it will not appear in ps output.
var will have the password for you to verify but you really shouldn't have plain text passwords saved anywhere for you to verify against. It is much better to put the password through a one-way hash and then compare the hash with something that you have saved in a file. For example:
var=$(head -n1 | md5sum)
Here, head will read one line (the password) and pass it to md5sum which will convert it to a hash. This hash can be compared with the known correct hash for this user's password. The text returned by head will be exactly what the user typed, unmangled by the shell.
Actually, for a known hash algorithm, it is possible to make a reverse look-up table for common passwords. So, the solution is to create a variable, called salt, that has some user dependent information:
var=$( { head -n1; echo "$salt"; } | md5sum)
The salt does not have to be kept secret. It is just there to make look-up tables more difficult to compute.
The md5sum algorithm, however, has been found to have some weaknesses. So, it should be replaced with more recent hash algorithms. As I write, that would probably be a sha-2 variant.
Again, if this is a sensitive application, do not use home-made tools
Answer to original question
how do I capture that within single quotes ('') when I can't state variables inside it like Var='$1'
The answer is that you don't need to. Consider, for example, this script:
#!/bin/sh
var=$1
echo $var
First, note that $$ and $? are both shell variables:
$ echo $$ $?
28712 0
Now, let's try our script:
$ bash ./script.sh '$$ $?'
$$ $?
These variables were not expanded because (1) when they appeared on the command line, they were in single-quotes, and (2) in the script, they were assigned to variables and bash does not expand variables recursively. In other words, on the line echo $var, bash will expand $var to get $$ $? but there it stops. It does not expand what was in var.
You can escape any dollar signs in a double-quoted string that are not meant to introduce a parameter expansion.
var=foo
# Pass the literal string fnSw38h$?2foo to script.sh
script.sh "fnSw38h\$?2$var"
You cannot do what you are trying to do. What is entered on the command line (such as the arguments to your script) must be in shell syntax, and will be interpreted by the shell (according to the shell's rules) before being handed to your script.
When someone runs the command script.sh fnSw38h$?2, the shell parses the argument as the text "fnSw38h", followed by $? which means "substitute the exit status of the last command here", followed by "2". So the shell does as it's been told, it substitutes the exit status of the last command, then hands the result of that to your script.
Your script never receives "fnSw38h$?2", and cannot recover the argument in that form. It receives something like "fnSw38h02" or "fnSw38h12", because that's what the user asked the shell to pass it. That might not be what the user wanted to pass it, but as I said, the command must be in shell syntax, and in shell syntax an unescaped and unnquoted $? means "substitute the last exit status here".
If the user wants to pass "$?" as part of the argument, they must escape or single-quote it on the command line. Period.

Bash script Arrow Keys produces weird characters

Consider the following minimised Bash Script:
echo Enter your name:
read NAME
echo $NAME
Now if I run the script and enter a name and want to navigate through my input with my arrow keys, [[D^ characters are getting returned.
How would you rewrite that script to cater for such behaviour, i.e. let me navigate with keys instead of winning an ASCII contest ?
These character sequences are the way that the terminal communicates that "cursor left" has been pressed. If the program receiving it does not interpret it as such and instead just displays them (after filtering the escape character), that's what you get.
Luckily for you, the read command of bash has the option -e to enable use of Readline for reading in the line. Readline performs all that handling (as it does on the normal bash command input).
Thanks to Andreas and some due dilligence with a search engine, I was able to rewrite my script:
echo Enter your name:
read -e NAME
echo $NAME
Now navigating through the input with arrow keys, works like a expected.
Here you can learn more about the read builtin command.

Prevent typed characters from being displayed (like disabling "echo" attribute in termios)

I'm writing a bash script in which I read single characters from the input. I do so using read -n 1 -s. -n 1 is to read only a single character; -s is "silent" mode, in which the typed characters won't be visible.
The problem is, that when the currently executed command isn't read (whenever some other commands in the bash script are being executed), the character gets displayed in the terminal.
This is the normal behaviour of a program in the terminal. To disable this, one normally disables the echo mode, for example using the termios library.
How can I achieve this in a bash script?
I prefer solutions in pure bash / Unix commands (without other scripting languages like python, perl etc.).
stty -echo
# Anything they type won't output here
stty echo
# Now it will

Resources