Bash script user input prompt - bash

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

Related

Bash script: any way to collect remainder of command line as a string, including quote characters?

The following simplified version of a script I'll call logit obviously just appends everything but $1 in a text file, so I can keep track of time like this:
$ logit Started work on default theme
But bash expansion gets confused by quotes of any kind. What I'd like is to do things like
$ logit Don't forget a dark mode
But when that happens of course shell expansion rules cause a burp:
quote>
I know this works:
# Yeah yeah I can enclose it in quotes but I'd prefer not to
$ logit "Don't forget a dark mode"
Is there any way to somehow collect the remainder of the command line before bash gets to it, without having to use quotes around my command line?
Here's a minimal working version of the script.
#!/bin/bash
log_file=~/log.txt
now=$(date +"%T %r")
echo "${now} ${#:1}" >> $log_file
Is there any way to somehow collect the remainder of the command line before bash gets to it, without having to use quotes around my command line?
No. There is no "before bash gets into it" time. Bash reads the input you are typing, Bash parses the input you are typing, there is nothing in between or "before". There is only Bash.
You can: use a different shell or write your own. Note that quotes parsing like in shell is very common, you may consider that it could be better for you to understand and get used to it.
you can use a backslash "\" before the single quote
$ logit Don\'t forget a dark mode

Echo but retain all quotes

Is it possible to echo a text and keep all quotes and double quotes in place?
I want to write a function to copy the text currently written in the terminal (completely with quotes).
Since I am on OSX I have to use pbcopy:
pb(){echo "$#" | pbcopy}
But pb osascript -e 'tell Application "iTerm" to display dialog "Job finished"' does return
osascript -e tell Application "iTerm" to display dialog "Job finished" but not
osascript -e tell 'Application "iTerm" to display dialog "Job finished"'.
The shell is removing the outer single quotes before pb ever sees the argument(s). Pass a single argument
pb "osascript -e 'tell Application \"iTerm\" to display dialog \"Job finished\"'"
to pb, and define it as
pb () {
printf '%s\n' "$1" | pbcopy
}
It would probably be just as easy to use a here document, though, rather than define a function that feeds its argument to pbcopy:
$ pbcopy <<'EOF'
osascript -e 'tell Application "iTerm" to display dialog "Job finished"'
EOF
Slightly more typing, but no need to nest so many quotes.
Is it possible to echo a text and keep all quotes and double quotes in place? I want to write a function to copy the text currently written in the terminal (completely with quotes).
Let's explore what you mean by "currently written in the terminal". If I understand correctly, you want to provide arbitrary input to a shell command at invocation time. In other words, you have a bit of text that you want to add into your copy buffer, and you want to send it to the stdin of pbcopy to do so.
As a solution to this particular problem, a shell function is a poor fit. That's because a shell function needs to be invoked with arguments that are subject to shell interpretation, and so you'll have to escape them carefully both when invoking pb and when defining it. These strings can be escaped. But it's inconvenient, for one thing because there are several special characters that need to be escaped in a double quoted string, but ' can't itself be escaped in a single quoted string.
Let's explore some other options.
$ pbcopy <<< "this is a simple one-line string directly from the command line. Since it's an argument to pbcopy it needs to be escaped."
$ pbpaste
this is a simple one-line string directly from the command line. Since it's an argument to pbcopy it needs to be escaped.
Here we tell the shell to provide the text to pbpaste's standard input. We still have to escape the string. But we don't have to pass it anywhere or correctly enquote it again to make it a valid shell argument.
Or we can provide multi-line string data to pbcopy without having to enquote it with this special here-doc syntax:
$ pbcopy <<-'-my-chosen-delimiter'
> Since this string's delimiter is single quoted,
> no interpolation will occur. That means " double quotes
> have no meaning, nor does ' single quotes, $dollar signs
> or other such meaningful bash syntaxes.
> -my-chosen-delimiter
$ pbpaste
Since this string's delimiter is single quoted,
no interpolation will occur. That means " double quotes
have no meaning, nor does ' single quotes, $dollar signs
or other such meaningful bash syntaxes.
I have thought that bash would be more powerful than that.
Well, on one hand, I think this is a very good opportunity to compare and contrast command line arguments (which are inherently positional by design and thus must be parsed and split by, usually, whitespace between arguments) to input and output streams expressed with | pipelines. I/O streams are designed to hold arbitrary data; it's not bash's fault that you wanted to make one into a shell-parsed variable list. It's not the considerable power of bash you're observing here, it's the functional limit of your bash knowledge.
But on the other hand, you're kind of right. The concessions to the user-interactive command line interface, substantial historical constraints to achieve backwards compatibility, and many valid design considerations made bash what it is. I for one find it and its ilk to be by far the most powerful user interface to a computer. But I wouldn't use it to assemble a complex application because, frankly, it's syntactically difficult. So don't expect bash to be something it's not. If you don't want to understand it's quirky and esoteric irregularities, stick with something more recent and more pedantic like python , go , ruby, node, or whatever non unix centric people run these days:P

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

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.

How to read "variable with backslash from stdin " for bash osx version

Yet another question about backslash being eaten up inside bash command/script, when the ultimate goal is to change backslash to forward slash.
There are several posts about this in stackoverflow. Most suggest using
"read -r"
And after playing around with quite a while, I am stuck. I am not sure whether it's a osx issue or bash issue I am hitting up.
However, in my mac bash using the following function
slashme ()
{
read -r varname;
echo "$varname";
echo "$1";
x="$(echo "$1" | sed 's.\\.\/.g')";
echo "$x";
}
and the following input
slashme "\\my\\precious"
> \my\precious
>
> /my/precious
What I would expect is following :
\\my\\precious
//my//precious
so, what is the magic switch I am missing ?
I am running
GNU Bash 3.2.57(1)-release (x86_64-apple-darwin-15)
\\ represents a literal backslash in a double-quoted string. Use single quotes:
slashme '\\my\\precious'
If you're using Bash, you don't even need the sed, Bash can do the string replacement by itself:
For example:
$ slashme() {
x=${1//\\/\/}
echo "$x";
}
$ slashme '\\my\\precious'
//my//precious
The point about having to quote the string to prevent the backslashes from being treated specially, stands.
If you don't want to add quotes, you could use read and enter the string after running the function:
$ slashme() {
read -r x
echo "${x//\\/\/}"
}
$ slashme
\\my\\precious
//my//precious
To complement the existing, helpful answers by addressing the OP's related desire not to have to enter quotes on the command line:
As ikkachu points out, there is no way around quoting arguments properly, as the shell interprets arguments before they're passed on to the target command.
You can make typing the necessary quotes simpler by defining readline macros that insert pairs of quotes and place the cursor between them - see below.
Add the following definitions to your ~/.inputrc file:
# Insert paired quotes and place the cursor between them.
# \e is ESC
# \e[D moves the cursor left
"\e\"": "\"\"\e[D"
"\e'": "''\e[D"
The next time you open a shell, you'll be able to type ESC, ' and ESC, " to insert a pair of single / double quotes and have the cursor placed between them.
Note that you first press ESC and then the quote character.
However, terminal applications can be configured to use the Alt (Option) key instead, which enables using the faster method of simultaneously pressing the macro trigger:
Alt+' to insert paired single quotes
Alt+" to insert paired double quotes
On macOS, open Terminal.app's preferences, select the profile of interest on the Profiles tab, click the Keyboard anchor, and select Use Option as Meta key; note, however, that this disables the usual, system-wide Option-based macOS keyboard shortcuts for producing character variations.
Of course, you're free to choose your own key-sequence / key-chord triggers.
See man bash, section READLINE.

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.

Resources