BASH command prompt: add characters behind command - bash

When editing PS1, one using escape sequences can manipulate the form of the prompt in almost anyway. Howver what holds true every time is that stdout starts right after the command input. For for simplicity's sake, here's an example of a simple prompt that adds some elements below the command entry:
PS1='aaaaaaaa\n\[\033[1B\]bbbbbbbb\n\[\033[2A\]\u#\h:\w\$ '
that looks more or less like this:
aaaaaaaa
user#hostname:~$ █
bbbbbbbb
Notice the position of the cursor once the drawing of the prompt is finished. The \033[ used in the PS1 variable manipulate the cursor (moving it up and down) to be able to draw the 'b' separator below the prompt and return to position.
If however a command is run, stdout as expected overwrites whatever is below the command:
aaaaaaaa
user#hostname:~$ echo 'hello '
hello bb
aaaaaaaa
user#hostname:~$ █
bbbbbbbb
The question is whether there is a way to manipulate the behavior of the prompt from the point of the command's last character in the following fachion:
<editable prompt><command><editable area after command?>
If for example I could instruct bash to print a newline or any escape sequence after every command that would solve the problem portrayed by the example.

This trick might work. I got the idea from https://superuser.com/questions/175799/does-bash-have-a-hook-that-is-run-before-executing-a-command
but I simplified as much as I could.
trap 'echo' DEBUG
The theory is simply to use bash's DEBUG trap to write a blank line before executing anything.

Related

How can I add a character before every capital letter in a terminal emulator's output (for a Braille display)?

TL;DR?
How can I change every instance of one character (e.g. 'E') in a terminal window to another string of characters (e.g. '~E'), moving all other characters along in the window in the process? So:
abcdEfghij
becomes:
abcd~Efghij
This should work in the gnome-terminal and work with whatever output is on that terminal, from whatever program. Ideally it will be a script or other program I can run within that terminal emulator.
The context
I am using a Braille display (the Canute 360) with a Braille screen-reader (brltty), which at present does not support capital letters. They show up in Braille the same as lower-case letters. Until this is developed for BRLTTY I sometimes need to be able to force showing which letters are capitalised in order for me to, for example, write code.
The proposed solution
N.B. The below proposed solution is not intended to be elegant; it is a quick and dirty way of letting me and other Braille-using developers continue to program with this display for our work until the proper solution is forthcoming in the screen-reader proper.
I am trying to essentially 'wrap' the output of the terminal emulator (in this case gnome-terminal to force a certain character in front of every capital letter so on the Braille display it can be identified. I am going to assume that character is tilde (~). I am not concerned about this breaking vertical alignment, or forcing the line off the edge of the display. I am assuming a 40 character wide terminal (width of the Canute).
So this normal output:
$ echo ${string}
A Quick Brown Fox
Jumps over the lazy
Dog. Etc.
$
Becomes this:
$ echo ${string}
~A ~Quick ~Brown ~Fox
~Jumps over the lazy
~Dog. ~Etc.
$
That would then be viewable on the Canute (in US Computer Braille) as this:
$ echo ${string}
~a ~quick ~brown ~fox
~jumps over the lazy
~dog. ~etc.
$
It is fine for this to be a command that has to be called first, like screen. So:
$ caps-hack
$ nano
[doing my thing, then quit nano]
$
[ctrl-x to quit caps-hack]
$
Or alternatively it could launch a new terminal window, or it could be tied to specific TUI applications (like nano). I will primarily be using it for working inside vi, nano, micro and other editors, so if it cannot capture all terminal output the solution is still valuable.
Example use case: Micro/nano text editor
When I need to see capitals whilst editing text using micro or nano I would first launch caps-hack, then I could use the TUI editor, exit it, be back on the terminal, then cancel caps-hack if I wanted to revert to usual behaviour.
This is what nano normally looks like:
GNU nano 4.8 New Buffer Modified
This is a nonsense file for
Stackoverflow.
^G Get Hel^O Write O^W Where I^K Cut Tex
^X Exit ^R Read Fi^\ Replace^U Paste T
I'm looking for a solution that would then make it look like this:
~G~N~U nano 4.8 ~New ~Buffer ~Modi
~This is a nonsense file for
~Stackoverflow.
^~G Get ~Hel^~O ~Write ~O^~W ~Where ~I^~
^~X ~Exit ^~R ~Read ~Fi^\ ~Replace^~U
(Note that I have cut it off at 40 characters.)
The effect would be the same (inserting tildes, cutting off at 40 characters) whether I was in the terminal itself, in mc, or watching a ping stream.
This active use case means that so far as I can see I cannot simply pipe the output of programs to a bash script (like the one below), as that wouldn't work with a TUI. So I cannot do: $ nano myfile.txt | caps-hack
What I have tried so far
I have not worked out how to essentially capture the output, modify it and write it back to the terminal window. However I have the following shell snippet which I believe could be used for it, if I know where to put it.
# Repeat for all visible lines in the terminal
moddedline1=$( echo ${originalLine1} | sed -E -e 's/\([A-Z]\)/~\1/g' )
moddedline1="${moddedline1:0:40}"
tput cup 1 0 && printf "${moddedline1}"

vim script leaves characters in stdin

I'm trying to use vim with -s option to run a script that replaces some lines in a file like this (text.txt):
test1
ab
ac
ae
test2
sd
Script file is like this (script):
:silent %s/test1\zs\_.\+\zetest2/\=substitute(submatch(0), '\n\(\w\)', '\n#\1', 'g')/g
:wq
It comments out lines between test1 and test2. Which is what I want. What I don't want though is output before and after prompt. I run it and get:
user#hostname: ~/vimtest$ vim -s script text.txt
^[[?1;2cuser#hostname: ~/vimtest$ 1;2c
So this ^[[?1;2c is bad news already but 1;2c is in the input as if I already typed it. If I hit enter it gives me a bash error. So I have to remove these symbols each time the script is used. Any ideas?
It seems like vim (or some vim startup script) is trying to figure out what type of terminal you are using. The ^[[?1;2c, with the last few characters left in the input buffer, is almost certainly part of your terminal emulator's response to a DA (Device Attributes) query. You can see this yourself by typing in bash:
printf '\033[c'
or, to see the complete return, pause a bit:
printf '\033[c'; sleep 0.1; echo
The response \033[?1;2c means "I'm a VT100 with Advanced Video Option.", which is what xterm and many other console programs respond. (The Linux console itself responds \033[?6c, which means "I'm a VT102.")
The reason that only 1;2c is left in the console input buffer, by the way, is that the initial escape code \033[? was ignored when it was read. The readline library will ignore it without echoing it, whereas normal console input will echo it and then ignore it; that's why the two shell commands above differ.
I can't reproduce this problem with my vim installation, so I don't really even know where to start looking. But you might try to see if disabling all startup files helps:
vim -u NONE -s script text.txt
If that helps, start disabling installed extensions one by one until you find the one which is causing the problem.
:%s/test1\zs\_.\+\ze\ntest2/\=substitute(submatch(0), '\n', '\n#', 'g')/g
:wq
this is tested here, it changed the input file in required way.
Some changes done based on your command:
add \n after \ze
in substitute() function we can just handle the \n, we don't need to capture the word after the \n
I noticed that you tagged the question with bash, so I thought a shell-solution should be accepted too.
awk '/test1/{p=1;print;next}/test2/{p=0;print;next}{$0=(p?"#":"")$0}7' file
this awk oneliner should do that for you. vim is very powerful editor, I love vim. But if you want to do some automatic transformation, I prefer a script or a proper text processing tool. On a linux box you can always find one. It is easier to test and debug.
Test with your input:
kent$ cat f
test1
ab
ac
ae
test2
sd
kent$ awk '/test1/{p=1;print;next}/test2/{p=0;print;next}{$0=(p?"#":"")$0}7' f
test1
#ab
#ac
#ae
test2
sd
If you want to save the text back to your file, you can :
awk '...' file > tmp.file && mv tmp.file file

zsh command line editor: go back to previous lines when entering a command

Note that I'm NOT talking about previous lines in the history. I'm talking about previous lines in the current multiline command.
When I type a multiline command in zsh, for instance (_ indicates the cursor):
$ for i in {1..5}; do
for> echo i_
At this point I might change my mind and want to let i loop through {1..10} instead, so I need to go back to the previous line. However, I can't figure out how to do this, as ⌫, or ←, or C-b, or whatever I can think of all stops at the beginning of the second line. So, is it possible at all to move back? Thanks in advance.
In fact, this is not limited to zsh. For instance, I've never figured this out in bash either.
I've already spent a fair amount of time Googling without any findings, so please excuse me and blame my Google-fu if this is obvious.
For your reference, I'm using Emacs keybinding, and
$ bindkey | grep delete
"^D" delete-char-or-list
"^H" backward-delete-char
"^[[3~" delete-char
"^?" backward-delete-char
Not sure whether this will help.
Here is how you would visual mode in bash's vim mode.
Inside .bashrc put the following lines.
set -o vi
# I do not remember which one of these is used by `v`, so I set both
export VISUAL=/usr/bin/nano
export EDITOR=/usr/bin/nano
Reload bash, and then push Esc to go into normal mode.
Type v, and nano should load. You can now enter your multiline command into nano. When you save and exit, the multiline command will be executed.
For zsh, the following lines in your rc may do it. I stole the last three lines from this answer and tested it on my zsh.
bindkey -v
autoload -U edit-command-line
zle -N edit-command-line
bindkey -M vicmd v edit-command-line
Actually this is possible.
While on that second line, pres Esc + X in order to execute a command,
and type push-line-or-edit (note that you can use Tab key for completion)
, then press return.
The prompt will change to a traditional one ditching your for> part from
continuation line, and you will have a new buffer filled with all your
previously typed lines, which of course now you can easily edit using well
known C-a or C-b keys.
I do not think this is possible, but here are other workarounds:
You can just write everything in one line. It is not easy to edit, but multiline strings are not easily editable either.
You can bind some key (e.g. <C-Enter> if you can tell your terminal not to output <C-m> in this case) to
function _-add-newline()
{
LBUFFER="$LBUFFER"$'\n'
}
zle -N add-newline _-add-newline
bindkey "\Ca" add-newline
. This way if you press <C-a> newline will appear in the buffer, but it will not trigger accept-line widget and previous line will still be editable. You can e.g. use left/right arrows to move to it.
If you type for x in y ; do<CR>echo foo<CR><C-c><Up> you will see for x in y ; do\necho foo in your buffer and all text will be editable. Note: you need <CR> to preserve last line (line aborted by <C-c> is not saved) and <C-c> to abort input; this variant will not work if you have already typed done (first <CR> will run the cycle). In last case you may discard last line with <C-c> and retype it.
Theoretically you may override accept-line widget with a code that parses your input and determines whether you have written command completely and if not then it will just append \n to the buffer (like in the above function) and do not run original accept-line, otherwise original accept-line is run. This variant is much harder to implement though, so I am saying “theoretically”.

Execute current line in Bash from Vim

This question is similar to Vim: execute current file?, but instead of executing the current file I want to execute only the current line.
Is this possible?
Ideally, I am looking for solutions which can have side effects in the outer shell.
For example, suppose I have the following line:
alias foo=bar
After running the command in Vim, if I start a shell with :sh, the alias foo is available, but if I quit vim using :q, then the alias is no longer available.
Sure thing, you can 'write' any content of the current file into the standard input of another program:
:.w !bash
Here . (the part before w) refers to the range of lines you are writing, and . is only the current line. Then you use !bash to write those lines to Bash.
I do this sort of thing all the time with:
:exec '!'.getline('.')
You can even create a mapping in your .vimrc:
nmap <F6> :exec '!'.getline('.')
Move the cursor to that line, and in normal mode press:
!!bash<cr>
This could be a comment if I can comment.
Concerning redirect/pipe lines of current buffer in Vim to external command, inspired by Daan Bakker's great answer, I wrote I answer here
(Save and run at the same time in Vim), on an question concerning
running a Python script (current buffer).
Beside running the whole buffer, how to run a range of line via an external command is demonstrated.
To save time, I just copy it below.
#####################
In Vim, you could simply redirect any range of your current buffer to an external command (be it 'bash', 'python', or you own Python script).
# Redirect the whole buffer to 'python'
:%w !python
Suppose your current buffer contains the two lines as below,
import numpy as np
print np.arange(12).reshape(3,4)
then :%w !python will run it, be it saved or not. And print something like below on your terminal,
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
Of course, you could make something persistent, for example, some keymaps.
nnoremap <F8> :.w !python<CR>
vnoremap <F8> :w !python<CR>
The first one runs the current line. The second one runs the visual selection, via the Python interpreter.
#!! Be careful, in Vim ':w!python' and ':.w !python' are very different, the
first writes (creates or overwrites) a file named 'python' with thew contents of
the current buffer. The second redirects the selected cmdline range (here dot .,
which mean current line) to an external command (here 'python').
For cmdline range, see
:h cmdline-ranges
Not the below one, which concerning normal command, not cmdline one.
:h command-range
This was inspired by Execute current line in Bash from Vim.
Add this mapping to your .vimrc file,
nmap <leader>E yyp:.!csh<CR>
Explanation:
yy
Yank current line
p
Paste yanked line below (and the cursor goes to this next row)
:.!csh<CR>
Execute (using csh) this newly pasted line in place. The output of this line replaces this current line, thus before executing the line was yanked and pasted below.
Consider the following command run on terminal,
seq 0 10 | xargs printf "%02x "
Expected output is,
00 01 02 03 04 05 06 07 08 09 0a
Consider you have this above command written in a file. To execute this command get this respective output back in that file, you can add this mapping in yours .vimrc,
nmap <leader>E :exec 'r!'.getline('.')<CR>
Note that, to execute above mentioned line, you need to write it with adding escape char for '%' as follows,
seq 0 10 | xargs printf "\%02x "
Go this line in your file and press <leader>E. In my case, <leader> is mapped to , hence I will press ,E
If I have a command on a line of text within vi/Vim like this"
ls -la
Position the cursor anywhere on the line and do
":.!!" and press return.
That is: colon dot bang bang return
That will execute the text in that line in the current shell from within vi/Vim and have the output inserted within the text file.
I'm thinking that is what you were asking? It's like magic.

Going backwards in a bash prompt

I'd like to have a blank line after my bash prompt and before the output on my Mac. It should look like this would:
echo; ls
Can I add a newline to my bash prompt and then go back up one line to wait for user input? Is there something obvious I'm missing?
I know this is old but for someone like me who came across this while googling for it. This is how you do this...
It's actually pretty simple!
Check out this link --> Cursor Movement
Basically to move up N number of lines:
echo -e "\033[<N>A HELLO WORLD\n"
Just change the "< N >" to however many lines you want to go back...
For instance, to move up 5 lines it would be "/033[5A"
To my knowledge this is not possible unless you delve into more low-level stuff like full-screen emulators like curses.
This is a bit of a stab in the dark, but you may be able to use VT102 terminal codes to control the cursor without having to use Curses. The relevant VT102 commands that you'd be interested in all consist of sending ESC, then [, then the specific command parameters.
For instance, to move the cursor up one line, one needs to output:
ESC [ 1 A
0x1B 0x5B 0x31 0x41
Be warned that the VT102 documentation generally uses octal, so keep an ascii table handy if you're using hex.
All of this advice is given without having tested it -- I don't know if VT102 commands can be embedded into your bash prompt, but I thought it might be worth a shot.
Edit: Yeah -- looks like a lot of people use VT102 formatting codes in their bash prompts. To translate my above example into something Bash would recognize, putting:
\e[1A
into your prompt should move the cursor up one line.
This is very possible. If your bash has C-v set as the readline quoted-insert command, you can simply add the following to your ~/.inputrc:
RETURN: "\C-e\C-v\n\C-v\n\n"
This wil make bash (readline, actually) insert two verbatim newlines before a regular interpreted newline. By default, only one is inserted, which is what causes output to start on the line after the prompt.
You can test if C-v is set to quoted-insert by typing it in bash (that's Ctrl+V) followed by e.g. an up arrow. This should print ^[[A or something similar. If it doesn't, you can bind it in ~/.inputrc too:
C-v: quoted-insert
RETURN: "\C-e\C-v\n\C-v\n\n"
~/.inputrc can be created if it doesn't exist. The changes will not take effect in running bashes unless you issue a readline re-read-init-file command (by default on C-x C-r). Be careful though. If you do something wrong, enter will no longer issue commands, and fixing your mistake could prove to be difficult. If you should do something wrong, C-o will by default also accept the line.
Adding a newline followed by moving the cursor back to the regular prompt (like you described) is possible, but will not have the effect you intend. The newline you inserted would simply be overwritten by the application output, since you moved the cursor back in front of it.
This works:
trap echo DEBUG
It doesn't add an extra newline if you hit return at an empty prompt.
The command above will cause a newline to be output for every member of a pipeline or multi-command line such as:
$ echo foo; echo bar
\n
foo
\n
bar
To prevent that so that only one extra newline is output before all command output:
PROMPT_COMMAND='_nl=true'; trap -- '$_nl && [[ $BASH_COMMAND != $PROMPT_COMMAND ]] && echo; _nl=false' DEBUG
The DEBUG trap is performed before each command so before the first command it checks to see if the flag is true and, if so, outputs a newline. Then it sets the flag to false so each command afterwards on the line doesn't trigger an extra newline.
The contents of $PROMPT_COMMAND are executed before the prompt is output so the flag is set to true - ready for the next cycle.
Because pressing enter on an empty command line still triggers the execution of the contents of $PROMPT_COMMAND the test in the trap also checks for those contents as the current command and doesn't perform the echo if they match.
I believe (but haven't tried) if you put '\n\b' in the prompt string it would do that.
In general, if you want to find out the codes to do anything a terminal can do, read the terminfo man page.
In this case, the cursor up one line code can be determined by:
tput cuu1
If you redirect the tput output to a file, you can see what control characters are used.
Bash also supports the PROMPT_COMMAND variable, allowing you to run arbitrary commands before each prompt is issued.

Resources