Bash completion using output from script - bash

I know there are a lot of tutorials on bash completion, but I just can't figure this out.
All I want is this. If I type myscript[tab][tab], then "myscript list-commands" is run. It will output a space delimited list of available commands (but I can output it however is appropriate). That output list is used for tab completion.
What do I put it my .bashrc to make that happen?

The easiest way is using a list of words/commands that your script supports:
Put the following into your .bashrc to have your script myscript support the commands add, list, delete:
complete -W "add list delete" myscript
This will leads to
> myscript [tab][tab]
add list delete
Hope this help. For further, more dynamic options than a simple wordlist have a look at the manpage of the complete command: https://ss64.com/osx/complete.html

Related

How do I edit current shell command without executing it?

There seems to be quite a lot of information on how to edit and execute a command using your editor using "edit-and-execute-command (C-x C-e)", but what I would like to achieve is take the current shell command, apply certain filtering (using a script) and then return it to prompt for further approval/manual changes before execution. Is this possible with bash?
Latest update based on my experience
The part 0"+y$dd in the following mapping is really something that you should carefully think about and tailor it to your taste/workflow/experience.
For instance, very frequently I've found myself ending up with multiple lines in the buffer, where I only want to execute the one the cursor is on; in this case I can use 0"+y$dd:%d<CR> instead of 0"+y$dd.
And this is just one of the possible scenarios.
Final answer for those who like vim
Set vim as your EDITOR/VISUAL, so that when editing a command line, you will use vim to edit it.
Put au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR> in your ~/.vimrc file to map Leaderd (which you will rarely use when editing a command) to the action "delete the current line into the + register without the trailing EOL".
you can use either the + or the * register in the mapping above; the ways to paste into the terminal will likely differ; you need the +clipboard option for these registers to be available.
When finished editing a command in the vim editor, hit EscapeLeaderd.
Paste the clipboard into the terminal (this is terminal-dependent).
Original answer
I often need to do the same, and I do it as follows. (I normally use the set -o vi in bash, so points 1 and 2 in the following are different if you use set -o emacs, the default; based on your question it looks like points 1 and 2 are unified in Ctrl+x followed by Ctrl+e, which is harder to type, imho.)
hit Escape to be in normal mode,
hit v to enter the editor to edit the command,
edit the command as I like,
(This is where you ask the question.)
hit Escape0"+y$dd:wq,
Note: 0"+y$, not simply "+yy, as the latter would copy the newline too, and this would result in executing the command upon pasting it in the command line,
paste the clipboard on the command line
how to do this depends on the terminal you are using, I guess; I hit Ctrl+Alt+v in URxvt.
proceed to approval/manual edit.
Clearly this is just a workaround, consisting in copying the edited command into the clipboard before deleting the whole command, so that nothing gets executed upon exiting the editor; however it's the best I can get for myself.
Update
As my EDITOR (and VISUAL) is equal to vim, when I edit the command, I edit it in vim.
In this respect, I have noticed that the buffer is named /tmp/bash-fc.random, where random is a 6-characters alphanumeric random string.
This gives space to a lot of possiblities, if you use vim as your editor, as you can define some mapping in your .vimrc to execute the whole sequence Escape0"+y$dd:wq. For instance, one command that you'd rarely use when editing a command line is Leaderd; therefore you can put the following mapping in your .vimrc file
au BufEnter /tmp/bash-fc.* nn <Leader>d 0"+y$dd:wq<CR>
so that step 4 in the above recipe becomes
hit EscapeLeaderd
It's not possible to do that in Bash/readline but it's possible in zsh
using edit-command-line command:
darkstar% autoload edit-command-line; zle -N edit-command-line
darkstar% bindkey "^X^E" edit-command-line
Now press Control-x Control-e to open your editor, edit line, leave the editor - you will see the updated command line but it will not be executed automatically.
Now that I think about it, maybe a variation of what #kenorb suggested in a comment is the best workaround (as it seems no solution exists), if we want to stick to bash.
What you can do is prepend a # (the comment character in bash) to the command, rather than echo. Then when you exit the editor, the command will be ineffective, and you will only have to press arrow up (or k, if you use set -o vi), remove the # and confirming.
Note that this strategy adds just a few keystrokes, so it can be fairly efficient, depending on your typing level.
These pieces might get you closer:
a) replace the the normal binding for newline newline (ctrl-M)
bind -x '"\C-M":date"
b) grab the current line from the history using !#
replace date with whatever script you want.
c) edit manually
d) if necessary, somehow tie in !!:p which prints the new command to the command line but does not execute it, thus letting you manually edit it.
e) using ctrl-J submit edited command rather than a newline
or they might not ....
There is an option in bash to modify command from history without executing it. I'm not sure it it's possible to use script for this, doesn't seem to be likely. Although, you can make modifications using history modifiers.
Enable option histverify to prevent execution of modified command
Use chain of modifiers to change last command
Use "!!" to put your result to command line for final edit
Here is how it looks:
$ shopt -s histverify
$ ls *.sh
script1.sh script2.sh script3.sh script-return.sh
$ !!:s/*/script1/:p
ls script1.sh
$ !!:s/1/2/:p
ls script2.sh
$ !!
$ ls script2.sh
script2.sh
I'd like to point you to the Composure framework for Bash (I'm not affiliated with it): https://github.com/erichs/composure
It provides draft and revise functions that sound like they could help with what you're trying to do. Here's a (long) quote from the project's readme file:
Composure helps by letting you quickly draft simple shell functions,
breaking down your long pipe filters and complex commands into
readable and reusable chunks.
Draft first, ask questions later
Once you've crafted your gem of a command, don't throw it away! Use
draft () and give it a good name. This stores your last command as a
function you can reuse later. Think of it like a rough draft.
$ cat servers.txt
bashful: up
doc: down
up-arrow
$ cat servers.txt | grep down
doc: down
$ draft finddown
$ finddown | mail -s "down server(s)" admin#here.com
Revise, revise, revise!
Now that you've got a minimal shell function, you may want to make it
better through refactoring and revision. Use the revise () command
to revise your shell function in your favorite editor.
generalize functions with input parameters
add or remove functionality
add supporting metadata for documentation
$ revise finddown
finddown ()
{
about finds servers marked 'down' in text file
group admin
cat $1 | grep down
}
$ finddown servers.txt
doc: down
It does not seem possible with a keyboard shortcut, at least:
$ bind -P | grep -e command -e edit
complete-command can be found on "\e!".
edit-and-execute-command can be found on "\C-x\C-e".
emacs-editing-mode is not bound to any keys
possible-command-completions can be found on "\C-x!".
vi-editing-mode is not bound to any keys
This can be done in native bash using readline specifically READLINE_LINE and READLINE_POINT variables. I use this functionality all the time though not through vim, you would need to get the value of $selected from your vim command and if not empty it takes your original line + your input and replaces your original line with the combination without executing. output as a variable
_main() {
selected="$(__coms_select__ "$#")"
origonal_text=$READLINE_LINE READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
}
bind -m emacs-standard -x '"\C-e": _main '
bind -m vi-command -x '"\C-e": _main '
bind -m vi-insert -x '"\C-e": _main '
Edit
Just remembered these two utilities that will let you do this as well.
Vipe allows you to run your editor in the middle of a unix pipeline and edit the data that is being piped between programs.
vp, up, vipe, Neomux (upgrade of nvim terminal) you can do some pretty neat throwing buffers between the terminal and split window.
and Athame (full vim on the command line)
https://github.com/ardagnir/athame
careful with that one though plugins work on the cli and it can get funky if you got tons of plugins

How do I map a certain input in bash to a command?

So I was wondering if it is possible for me to map a certain input to a command in gnome terminal. For example, when I type "foo" in the command shell, it would automatically execute a certain command like going to a directory where a program is located and execute that program in a specific configuration.
Yes, it is called an alias:
A Bash alias is essentially nothing more than a keyboard shortcut, an
abbreviation, a means of avoiding typing a long command sequence. If,
for example, we include alias lm="ls -l | more" in the ~/.bashrc file,
then each lm [1] typed at the command-line will automatically be
replaced by a ls -l | more. This can save a great deal of typing at
the command-line and avoid having to remember complex combinations of
commands and options. Setting alias rm="rm -i" (interactive mode
delete) may save a good deal of grief, since it can prevent
inadvertently deleting important files.
So basically:
alias foo="cd /path/to/dir; ./myprogram; cd -"
cd - is following #Cyrus's suggestion - to return you to the directory you started from. This is safer and more expected of most commands, but of course, you can use whatever you like.

How to execute command inside vim?

I want to run shell script inside Vim editor.
I heard it is possible but do not know.
Command:./shell.sh inside vim.
There are multiple ways to do it. A primary question is "Do you want the output from the script in the file?"
If you want the output in the file:
:r!./shell.sh
If you don't want the output in the file:
:!./shell.sh
If you have the line ./shell.sh in the file, you can include the output in the file with:
!!sh
If you've done it before, you have more options.
If you save the command in a named buffer you have still more options.
If you want the script to have a portion of the file (edit buffer) as its standard input, you have an enormous number of options you can use in conjunction with either of these mechanisms.
Prefix the command with a !. For example, open Vim and write:
:!ls
This will execute the shell ls command.
Note that you'll have to be in the correct directory within Vim for this to work.

bash tab completion filter out options

I would like to make tab completion in bash a bit more intelligent.
Let's say I have a folder with a src file .lisp, and a compiled version of that file .fasl. I would like to type vi filename [tab tab], and the .lisp autocompletes as the only option. That is, it's not likely that I want vim to open a compiled binary, so don't have it in the list of autocomplete options to cycle through.
Is there a way that I can keep a flat list of extensions that autocomplete ignores, or somehow customize it for vim, so that autocomplete ignores only particular file extensions when a bash command starts with vi ...
Any ideas are appreciated.
Thanks!
From man bash:
FIGNORE
A colon-separated list of suffixes to ignore when performing
filename completion. A filename whose suffix matches one of the entries in FIGNORE is excluded from the
list of matched filenames. A sample value is ".o:~" (Quoting is
needed when assigning a value to this variable, which contains
tildes).
So, for your example this can be set in your .bashrc file with
FIGNORE=".o:~:.fasl"
or, if you want to keep any other site-wide settings:
FIGNORE=".o:~:.fasl:$FIGNORE"
The bash complete command seems to be what you want.
Here is a Linux Journal link to 'complete' command video. and here is the follow-up page More on Using the Bash Complete Command
The links explain it quite well, and here is a related SO Question/Answer: Bash autocompletion across a symbolic link

Can bash completion be invoked programmatically?

What I want is a function I can call from a program so it completes the way bash would given a commandline and a location where TAB was pressed.
. /etc/bash_completion
generate_completions "command arg1 arg2" 17
would return the same thing as
command arg1 arg2[TAB]
I haven't seen any way to do this.
I actually had to do this to figure out how apt-get autocomplete works on ubuntu (built my own pseudo-repository tool :)
This is a multistep process:
First, complete -p will give you a listing of all completions in the form of a set of commands you can run to replicate the configuration. For example, lets say you want to hunt down the autocomplete for apt-get. Then:
$ complete -p | grep apt-get
complete -F _apt_get apt-get
This tells you that the shell function _apt_get is called by the completion mechanism.
You need to recreate the special variables used by the function,, namely COMP_LINE (the full line), COMP_WORDS (bash array of all of the arguments -- basically split COMP_LINE), COMP_CWORD (index, should point to last value), COMP_POINT (where within the word you are doing the autocomplete), and COMP_TYPE (this is how you tell it that you want to complete as if you hit tab).
Note: read the manpage for more info -- this is how i figured it out in the first place. man bash

Resources