How to add something at the beginning of every Bash command? - linux

I'd like to change my Bash configuration, so when I type something (e.g. foo bar) in the command prompt, it really executes h foo bar.
I want to do it because I often use hilite (aliased as h) to color stderr in red, and I would like to make this behaviour permanent.
Other use I see would be interacting with Git, as I write lots of commands like:
git status
git add ...
git commit ...
I guess I could use preexec_invoke_exec to execute something before
the command is being run, but I don't know how can I change the
command or prevent it from executing.
Any ideas?

You can achieve this by binding the return key to insert the h for you. You can do this by adding this to your .input.rc:
Return: "\C-ah\ \n"
or put this bind in your .bashrc:
bind 'RETURN: "\C-ah \n"'
(Kudos to these guys).
There are a few catches: obviously, it's bash-only, and this can give some pretty strange behavior in places (I can't think of a decent example right now), so I wouldn't say this is 'good' bashing in any way.
I would personally skip hilite and keep it all pure bash. So instead, try to look for a way to append something to each command so as to redirect the stderr stream to a colorized echo/printf...but that's a matter of preference I guess :)

To run a different command from the one you've specified, your best bet may be to maks a bash function of the commands to be caught.
I don't know anything about "hilite", but if it installs a binary, at, say /usr/bin/hilite, you could use:
git () {
/usr/bin/hilite /usr/bin/git "$#"
}
So ... when you run git at your bash prompt, bash will actually run hilite, using /usr/bin/git and the rest of your command line arguments as the arguments to h.
Note that this should work in traditional Bourne shell as well as bash.

Related

Can I react on entered command in bash?

I would like to configure my bash in a way so that I react on the event that the user enters a command. The moment they press Enter I would like my bash to run a script I installed first (analog to any PROMPT_COMMAND which is run each time a prompt is given out). This script should be able to
see what was entered,
maybe change it,
maybe even make the shell ignore it (i. e. make it not execute the line),
decide on whether the text shall be inserted in the history or not,
and maybe similar things.
I have not found a proper way to do this. My current implementations are all flawed and use things like debug traps to intervene before executing a command or (HISTTIMEFORMAT='%s '; history 1) to ask the history after the command execution is complete about things when the command was started etc (but that is only hindsight which is not really what I want).
I'd expect something like a COMMAND_INTERCEPTION variable which would work similar to PROMPT_COMMAND but I'm not able to find anything like it.
I also considered to use command line completion to achieve my goal but wasn't able to find anything about reacting on sending a finished command in this, but maybe I just didn't find it.
Any help appreciated :)
You can use the DEBUG trap and the extdebug feature, and peek into BASH_COMMAND from the trap handler to see the running command. (Though as noted in comments, the debug trap is sprung on every simple command, not every command line. Also subshells elude it.)
The debug handler can prevent the command from running, but can't change it directly. Though of course you could run any command inside the debugger, possibly using BASH_COMMAND and eval to build it and then tell the shell to ignore the original command.
This would prevent running anything starting with ls:
$ preventls() { case "$BASH_COMMAND" in ls*) echo "no!"; return 1 ;; esac; }
$ shopt -s extdebug
$ trap preventls DEBUG
$ ls -l
no!
Use trap - DEBUG to remove the trap. Tested on Bash 4.3.30.

Is it possible to copy the tab completion of a command for my linux function?

Let's say I have a bash shell function named magic. I want to define a tab completion function _magic which would allow magic to piggyback on the tab completion functions of any given command (if available). In other words, I want magic to be able to do something like this:
~ $ magic git ... <search for _git and use it if found>
~ $ magic cd ... <search for _cd and use it if found>
~ $ magic some-cmd ... <search for _some-cmd and use it if found>
I can't seem to find anything online that would help me achieve this using compgen, complete etc. Is this even possible? Thanks in advance.
There are many such (pseudo?) commands, that need this. e.g. time, nice, strace etc.
They all use the same command completion: _command. So, don't re-invent the wheel. :)
Try running this in your bash terminal, if this work:
complete -F _command magic

Alter environment after a command

Altering the environment just for a single command is very simple:
DB=postgresql some_command --with --arguments
Unfortunately, I have to do this on a remote server and due to limitations of the deployment, I can only edit what comes after the some_command. The following would be nice, but doesn't do the trick (in Bash):
some_command --with --arguments DB=postgresql
Is there some other Bash hack to get there?
Here's another idea, a bit wild I'm afraid:
some_command --do-nothing `DB=postgresql some_command --now-really`
The idea is that the backquoted command will actually do what you want. The first some_command is only there so the command will start as you want it. You should find parameters that would make it do something harmless.
If you have nothing equivalent for the --do-nothing parameter, you can do this:
some_command `DB=postgresql some_command --now-really; ps-grep-kill`
Where ps-grep-kill is a combination of these commands (I leave the details as an exercise), which finds the parent process, which is just about to run some_command, and kills it before it gets a chance to (but after the backquoted some_command has run already).
Can you execute export DB=postgresql to modify the environmental variables globally? Then you can run subsequent commands that pick up the new environmental variable.
If you just run the command, it can't change the environment.
But if you source it, it can:
source some_command --with --arguments DB=postgresq
Shorthand:
. some_command --with --arguments DB=postgresql
I prefer not to source large scripts this way, because then they may end up changing more than you intended. So I'd write two scripts - one finds what you want to change, and outputs it. The other is a very small one, that runs the first one (normally, without source) and does the changes.
But re-reading your question, it seems like you can't use source. So I see no way to do what you want.

Bash: call script with customized keyboard shortcuts?

Lets say I have a script, "myscript.sh", with contents being simply echo $PWD. I'd like to bind somehow this script to a key combo in bash (gnome-terminal) - so that when I press this key combination, the output of "myscript.sh" is inserted ("pasted") at the cursor position in the terminal.
Apparently, bash history and line manipulation is handled by readline - and the references I got for bash keyboard shortcuts, do reference readline:
bash keyboard shortcuts
Bash Reference Manual: Bindable Readline Commands
I've also seen in Bash Reference Manual: Readline Init File Syntax that the key bindings for bash can be listed by using bind -p (see help bind [not 'man bind'] for more). So maybe this question would better be titled as "_binding macros to custom keyboard shortcuts in readline" :) But in any case, is what I want possible to do?
I guess an alternative would be to have the script be something like "pwd | xsel -b", and then I call it on terminal - and I can paste afterwards; but I'd still like a single keyboard shortcut instead, say like Ctrl-Alt-H (which seems to be not used for anything), which will immediately insert/paste script output when pressed.
Thanks in advance,
Cheers!
EDIT: Just to clarify - here is my use case where I'd like this facility. I'm usually cd'd in a project folder, usually named something like myproject-folder-0012a, which is under revision control by svn. And there is a bunch of these folders. So quite often, I do commits where the first word of the message is the directory name, as in:
svn ci -m "myproject-folder-0012a: here a commit message"
But that is what I don't like - first I type 11 characters, which go rather fast:
svn ci -m "
And then, I cannot use autocompletion to get the name (i'm inside the folder) - which means I either have to fully type it (no way :)), or I copy paste it from the prompt (which requires selection - press mouse, drag, release mouse; then Ctrl+Shift+C, and then Ctrl+Shift+V, plus any left/right keys if I miss allignment - plus deletions and such if I make the copy wrong).
Meaning - so much work, just to get the bloody folder name for a bloody commit message :( I'd MUCH rather press something like (say) Ctrl-Alt-H, and have the folder name automatically inserted at cursor position, and be done with it :)
My suggestion for xsel is only because I could put it into a "global" script - say symlink it as /usr/bin/myscript (and obviously, the contents of the script are echo $(basename $PWD) rather than just pwd for my needs), and then I could do:
$ myscript # this puts directory name in clipboard
$ svn ci -m "[CTRL+SHIFT+V TO PASTE HERE]myproject-folder-0012a[NOW TYPE]: here a commit message"
... which sort of makes the workload less, but still - then I have to remember what the script name is, and call it, before I type the svn command (and I don't always remember that)... And still - I have to call a command, and then press a key combo; why shouldn't I just press a key combo once, and be done with it ??! :)
Well, hope this clarifies my problem a bit better ....
EDIT2: However, another reason why a bash keyboard shortcut would be useful, is that then I could also "paste/insert current directory name" not only in shell commands - but also in terminal programs, say like nano (where it would, arguably, be more difficult to use bash script or function expansion directly).
Simple version:
This command at a shell prompt:
bind '"\ee": "${PWD##*/}\e\C-e"'
or this line added to your ~/.inputrc:
"\ee": "${PWD##*/}\e\C-e"
will cause Alt-e to insert the basename of the current directory on the command line. It requires that the default binding of the readline function shell-expand-line which is \e\C-e be present (this could be adapted if it's different). I'm also making the assumption that you're using Bash's emacs mode.
Unfortunately, it causes things that have already been typed to be expanded as well. One of the affects of this is that after having typed:
svn ci -m "
and pressing Alt-e, the quotation mark will have disappeared. There are a couple of ways to deal with this.
One, assume that all you'll lose is the quote and either manually add it back or have the readline macro add it for you:
bind '"\ee": "${PWD##*/}\e\C-e\eb\"\C-e"'
which just isn't very satisfactory.
Advanced version:
Or, two, kill the line, do the insertion, then yank the line back:
bind '"\ee": " \C-u \C-a\C-k${PWD##*/}\e\C-e\C-y\C-a\C-y\ey\b"'
or
bind '"\ee": " \C-u \C-a\C-k${PWD##*/}\e\C-e\C-y\C-a\C-y\ey\b\ef\C-f"'
This leaves the rest of the line intact (nothing else is expanded or deleted), but it uses the kill ring, so it may leave it in a state that's different than you expect (if you're using it). It also inserts a space after the inserted directory name (the spaces in the macro are used to ensure that older kill-ring contents are not regurgitated if the macro is executed at the beginning or end of the line). The macro should work regardless of the position of the cursor in the line. The insertion will be made at the cursor's position, leaving the cursor in the same position [in the first version].
Edit: The second version leaves the cursor after the dirname and space that are inserted.
Edit 2:
The readline function shell-forward-word (unbound) does a better job than forward-word (\ef) for this. You can make use of that like this:
bind '"\ew":shell-forward-word'
bind '"\ee": " \C-u \C-a\C-k${PWD##*/}\e\C-e\C-y\C-a\C-y\ey\b\ew\C-f"'
By the way, you should know that Bash keyboard shortcuts are not active in other programs such as nano.
Ok, not really an answer, but I'd just like to summarize the comments I got so far, which are useful for my problem. However, the question as it stands - in respect to bash keyboard shortcuts running arbitrary scripts - is still not answered (I'd still prefer doing all this with a single key combo :))
First, I can use a 'global' script like:
$ sudo bash -c 'cat > /usr/bin/bpwd <<EOF
#!/bin/bash
basepwd=\$(basename \$(pwd))
echo -n \$basepwd # suppress line ending
# exec 1>/dev/null # debug: redir stdout to null
echo -n \$basepwd | xsel -i -b # suppress LF, and make xsel read from stdin
# exec 1>/dev/tty # debug: restore stdout
EOF
chmod +x /usr/bin/bpwd'
Or, I can add bash functions to my .bashrc (note: make sure you reload bash after you add these lines to .bashrc - for example, simply by typing bash in your current terminal):
$ echo '
bpwd2() { basepwd=${PWD##*/} ; echo -n $basepwd | xsel -i -b ; echo -n $basepwd ; }
svnci-test() { echo -n "$(bpwd2): $*" ; }
svnci-m() { svn ci -m "$(bpwd2): $*" ; }' >> ~/.bashrc
Basically, I misunderstood Reese Moore's suggestion originally - you can indeed use backticks - consider this command session (after the above commands have been ran):
$ bpwd
Desktop\
$ bpwd2
Desktop\
$ echo `bpwd`
Desktop
$ echo "`bpwd2` 2"
Desktop 2
This is what I needed to understand Moore's "the output from the backticked commands will be used as input on the executed command" (however, one also needs to take care to clean the line endings from the output); or, in my case, I can call
svn ci -m "`bpwd`: my message here"
# svn ci -m "${PWD##*/}: my message here" # alternatively
... or, I could follow camh's suggestion, and use svnci-m as a function (in my case, I almost never use additional arguments to svn ci, and so my version is slightly different). And to test whether arguments are passed correctly, I can use the svnci-test function:
$ svnci-test "my message"
Desktop: my message\
Thanks for the comments so far,
Cheers!
One way to do what you want with a single key press is to take advantage of programmable completion in bash. You possibly have some programmable completion set up with the bash_completion tool/package. If not, look into that to see the specifics of how it is done.
The idea is to have the programmable completion recognise when you have hit at the start of a svn commit message and then have it return a single completion which is the text you want to insert (the basename of the current directory).
I've only dabbled with programmable completion so I can't give you the details, but the above-mentioned bash_completion package or the subversion completion script may be a good start.

Creating a shorter version of a bash command

i am novice to the Linux shell and had to recently start using it for work...i have now got used to the basic commands in bash to find my way around...however there are a lot of commands i find myself typing over and over again and its kind of a hassle to type them every time...so can anyone tell me how can i shorten the command syntax for ones i use frequently.
A very simple example, i use the ls -lh command often, though this is quite short but im just giving an example. Can I have something (a shell script may be) so that I can run it by typing just say lh.
I want to do it for more complex commands.
alias lh='ls -lh'
If you want to make this persistent across sessions, put it in your .bashrc file. Don't forget to run source .bashrc afterwards to make bash aware of the changes.
If you want to pass variables, an alias just isn't enough. You can make a function. As an example, consider the command lsall to list everything in a given directory (note this is just an example and thus very error prone):
function lsall
{
ls $1/*
}
$Ngets replaced with the Nth argument.
You would place the following alias in your .bashrc file:
alias lh='ls -lh'
Now lh is shorthand for ls -lh.
For more complicated tasks you could use a bash function. For example, on one of my machines I have a function which causes 'ls' to run after every successful 'cd':
cdls() {
builtin cd "$*" && ls
}
alias cd='cdls'
you can define aliases. For longer commands, use a function, put it into a library file and source it whenever you want to use your functions.
Just for the sake of completeness, since you want to learn bash: you could also write a function
lh() {
ls -lh "$#"
}
although I would never write that when a simple alias would do ;-)
;) Heh, I remember one problem when I was starting out on Linux, which is that I would ask questions like these, and people would diligently answer them, but no one would explain how to make such changes permanent, and so I found myself typing in a bunch of commands every time I opened a terminal.
So, even though others have accurately answered this question... if you want to make the change permanent, put the alias-line into your ~/.profile or ~/.bashrc file (~ = your home directory). It depends a bit on your distribution on which is run when, but I always try adding my aliases to ~/.profile first and if that doesn't work, then ~/.bashrc. One of them should work for sure.

Resources