Customize tab completion in shell - linux

This may be have a better name than "custom tab completion", but here's the scenario:
Typically when I'm at the command line and I enter a command, followed with {TAB} twice, I get a list of all files and subdirectories in the current directory. For example:
[user#host tmp]$ cat <TAB><TAB>
chromatron2.exe Fedora-16-i686-Live-Desktop.iso isolate.py
favicon.ico foo.exe James_Gosling_Interview.mp3
However, I noticed at least one program somehow filters this list: wine. Consider:
[user#host tmp]$ wine <TAB><TAB>
chromatron2.exe foo.exe
It effectively filters the results to *.exe.
Thinking it might be some sort of wrapper script responsible for the filtering, a did a which and file an it turns out wine is not a script but an executable.
Now, I don't know whether this "filter" is somehow encoded in the program itself, or otherwise specified during the default wine install, so I'm not sure whether this question is more appropriate for stackoverflow or superuser, so I'm crossing my fingers and throwing it here. I apologize if I guessed wrong. (Also, I checked a few similar questions, but most were irrelevant or involved editing the shell configuration.)
So my question is, how is this "filtering" accomplished? Thanks in advance.

You will likely find a file on your system called /etc/bash_completion which is full of functions and complete commands that set up this behavior. The file will be sourced by one of your shell startup files such as ~/.bashrc.
There may also be a directory called /etc/bash_completion.d which contains individual files with more completion functions. These files are sourced by /etc/bash_completion.
This is what the wine completion command looks like from the /etc/bash_completion on my system:
complete -f -X '!*.#(exe|EXE|com|COM|scr|SCR|exe.so)' wine
This set of files is in large part maintained by the Bash Completion Project.

You can take a look at Programmable Completion in bash manual to understand how it works.

I know this is old but I was looking to do something similar with a script of my own.
You can play around with an example I made here:
http://runnable.com/Uug-FAUPXc4hAADF/autocomplete-for-bash
Pasted code from above:
# Create function that will run when a certain phrase is typed in terminal
# and tab key is pressed twice
_math_complete()
{
# fill local variable with a list of completions
local COMPLETES="add sub mult div"
# you can fill this variable however you want. example:
# ./genMathArgs.sh > ./mathArgsList
# local COMPLETES=`cat ./mathArgsList`
# we put the completions into $COMPREPLY using compgen
COMPREPLY=( $(compgen -W "$COMPLETES" -- ${COMP_WORDS[COMP_CWORD]}) )
return 0
}
# get completions for command 'math' from function '_math_complete()'
complete -F _math_complete math
# print instructions
echo ""
echo "To test auto complete do the following:"
echo "Type math then press tab twice."
echo "You will see the list we created in COMPLETES"
echo ""

Related

How to get a filename in a bash script without the keyboard

I want to write a script for linux, that will first copy a movie/series file to cache with something like:
cat /filepath/filename > /dev/null
and than open the same file in vlc.
The problem is getting the file name and path in to the script. I would like to simply double click a file, or somehow make this a faster process than typing this manually (especially because the file names of some series are just inconsistent and hard to type, even with auto-complete).
This is useful for watching movies or series on a laptop/netbook, since it allows the disk to spin down.
You should be able to create your own 'program' in a bash script which takes its first argument to be the filename using the convention "$1".
The bash script should look something like the below. I tested it, storing the script in the file cachedvlc.sh. The inverted commas helping to handle whitespace and weird characters...
#!/bin/bash
cat "$1" > /dev/null
vlc "$1"
...and will need to be made executable by changing its permissions through the file manager or running this in the terminal...
chmod u+x cachedvlc.sh
Then within your operating system, associate your bash script with the type of file you want to launch. For example on Ubuntu, you could add your script and call it 'Cached VLC' to the Menu using the 'Main Menu' application, then right-click on the file in Nautilus and choose 'Open with' to select your bash script.
After this, double-clicking or right-clicking on a file within your filemanager should be good enough to launch a cached view. This assumes what you say about caching is in fact correct, which I can't easily check.

Scripting on Linux

I am trying to create a script that will run a program on each file in a list. I have been trying to do this using a .csh file (I have no clue if this is the best way), and I started with something as simple as hello world
echo "hello world"
The problem is that I cannot execute this script, or verify that it works correctly. (I was trying to do ./testscript.csh which is obviously wrong). I haven't been able to find anything that really explains how to run C Scripts, and I'm guessing there's a better way to do this too. What do I need to change to get this to work?
You need to mark it as executable; Unix doesn't execute things arbitrarily based on extension.
chmod +x testscript.csh
Also, I strongly recommend using sh or bash instead of csh, or you will soon learn about the idiosyncrasies of csh's looping and control flow constructs (some things only work inside them if done a particular way, in particular with the single-line versions things are very limited).
You can use ./testscript.csh. You will however need to make it executable first:
chmod u+x testscript.csh
Which means set testscript to have execute permissions for the user (who ever the file is owned by - which in this case should be yourself!)
Also to tell the OS that this is a csh script you will need put
#! /path/to/csh
on the first line (where /path/to/csh is the full path to csh on your system. You can find that out by issuing the command which csh).
That should give you the behvaiour you want.
EDIT As discussed in some of the comments, you may want to choose an alternative shell to C Shell (csh). It is not the friendliest one for scripting.
You have several options.
You can run the script from within your current shell. If you're running csh or tcsh, the syntax is source testscript.csh. If you're running sh, bash, ksh, etc., the syntax is . ./testscript.sh. Note that I've changed the file name suffix; source or . runs the commands in the named file in your current shell. If you have any shell-specific syntax, this won't work unless your interactive shell matches the one used by the script. If the script is very simple (just a sequence of simple commands), that might not matter.
You can make the script an executable program. (I'm going to repeat some of what others have already written.) Add a "shebang" as the first line. For a csh script, use #!/bin/csh -f. The -f avoids running commands in your own personal startup scripts (.cshrc et al), which saves time and makes it more likely that others will be able to use it. Or, for a sh script (recommended), used #!/bin/sh (no -f, it has a completely different meaning). In either case, run chmod +x the_script, then ./the_script.
There's a trick I often use when I want to perform some moderately complex action. Say I want to delete some, but not all, files in the current directory, but the criterion can't be expressed conveniently in a single command. I might run ls > tmp.sh, then edit tmp.h with my favorite editor (mine happens to be vim). Then I go through the list of files and delete all the ones that I want to leave alone. Once I've done that, I can replace each file name with a command to remove it; in vim, :%s/.*/rm -f &/. I add a #!/bin/sh at the top save it, chmod +x foo.sh, then ./foo.sh. (If some of the file names might have special characters, I can use :%s/.*/rm -f '&'/.)

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.

Edit shell script while it's running

Can you edit a shell script while it's running and have the changes affect the running script?
I'm curious about the specific case of a csh script I have that batch runs a bunch of different build flavors and runs all night. If something occurs to me mid operation, I'd like to go in and add additional commands, or comment out un-executed ones.
If not possible, is there any shell or batch-mechanism that would allow me to do this?
Of course I've tried it, but it will be hours before I see if it worked or not, and I'm curious about what's happening or not happening behind the scenes.
It does affect, at least bash in my environment, but in very unpleasant way. See these codes. First a.sh:
#!/bin/sh
echo "First echo"
read y
echo "$y"
echo "That's all."
b.sh:
#!/bin/sh
echo "First echo"
read y
echo "Inserted"
echo "$y"
# echo "That's all."
Do
$ cp a.sh run.sh
$ ./run.sh
$ # open another terminal
$ cp b.sh run.sh # while 'read' is in effect
$ # Then type "hello."
In my case, the output is always:
hello
hello
That's all.
That's all.
(Of course it's far better to automate it, but the above example is readable.)
[edit] This is unpredictable, thus dangerous. The best workaround is , as described here put all in a brace, and before the closing brace, put "exit". Read the linked answer well to avoid pitfalls.
[added] The exact behavior depends on one extra newline, and perhaps also on your Unix flavor, filesystem, etc. If you simply want to see some influences, simply add "echo foo/bar" to b.sh before and/or after the "read" line.
Try this... create a file called bash-is-odd.sh:
#!/bin/bash
echo "echo yes i do odd things" >> bash-is-odd.sh
That demonstrates that bash is, indeed, interpreting the script "as you go". Indeed, editing a long-running script has unpredictable results, inserting random characters etc. Why? Because bash reads from the last byte position, so editing shifts the location of the current character being read.
Bash is, in a word, very, very unsafe because of this "feature". svn and rsync when used with bash scripts are particularly troubling, because by default they "merge" the results... editing in place. rsync has a mode that fixes this. svn and git do not.
I present a solution. Create a file called /bin/bashx:
#!/bin/bash
source "$1"
Now use #!/bin/bashx on your scripts and always run them with bashx instead of bash. This fixes the issue - you can safely rsync your scripts.
Alternative (in-line) solution proposed/tested by #AF7:
{
# your script
exit $?
}
Curly braces protect against edits, and exit protects against appends. Of course, we'd all be much better off if bash came with an option, like -w (whole file), or something that did this.
Break your script into functions, and each time a function is called you source it from a separate file. Then you could edit the files at any time and your running script will pick up the changes next time it gets sourced.
foo() {
source foo.sh
}
foo
Good question!
Hope this simple script helps
#!/bin/sh
echo "Waiting..."
echo "echo \"Success! Edits to a .sh while it executes do affect the executing script! I added this line to myself during execution\" " >> ${0}
sleep 5
echo "When I was run, this was the last line"
It does seem under linux that changes made to an executing .sh are enacted by the executing script, if you can type fast enough!
An interesting side note - if you are running a Python script it does not change. (This is probably blatantly obvious to anyone who understands how shell runs Python scripts, but thought it might be a useful reminder for someone looking for this functionality.)
I created:
#!/usr/bin/env python3
import time
print('Starts')
time.sleep(10)
print('Finishes unchanged')
Then in another shell, while this is sleeping, edit the last line. When this completes it displays the unaltered line, presumably because it is running a .pyc? Same happens on Ubuntu and macOS.
I don't have csh installed, but
#!/bin/sh
echo Waiting...
sleep 60
echo Change didn't happen
Run that, quickly edit the last line to read
echo Change happened
Output is
Waiting...
/home/dave/tmp/change.sh: 4: Syntax error: Unterminated quoted string
Hrmph.
I guess edits to the shell scripts don't take effect until they're rerun.
If this is all in a single script, then no it will not work. However, if you set it up as a driver script calling sub-scripts, then you might be able to change a sub-script before it's called, or before it's called again if you're looping, and in that case I believe those changes would be reflected in the execution.
I'm hearing no... but what about with some indirection:
BatchRunner.sh
Command1.sh
Command2.sh
Command1.sh
runSomething
Command2.sh
runSomethingElse
Then you should be able to edit the contents of each command file before BatchRunner gets to it right?
OR
A cleaner version would have BatchRunner look to a single file where it would consecutively run one line at a time. Then you should be able to edit this second file while the first is running right?
Use Zsh instead for your scripting.
AFAICT, Zsh does not exhibit this frustrating behavior.
usually, it uncommon to edit your script while its running. All you have to do is to put in control check for your operations. Use if/else statements to check for conditions. If something fail, then do this, else do that. That's the way to go.
Scripts don't work that way; the executing copy is independent from the source file that you are editing. Next time the script is run, it will be based on the most recently saved version of the source file.
It might be wise to break out this script into multiple files, and run them individually. This will reduce the execution time to failure. (ie, split the batch into one build flavor scripts, running each one individually to see which one is causing the trouble).

bash script to show compatible commands based on "Windows-speak"

Problem: Customer X is a Windows user who wants to be able to trigger pre-packaged bash commands by using mnemonic keywords or "tag hints" when she is logged in to her RedHat box via shell.
Example: Customer X logs into host using ssh and wants to do some routine file operations. She wants to be able to type
copy file
and get back a listing of pre-fab fill-in-the-blank bash commands to choose from
cp <#source#> <#dest#> ### simple copy
cp -R <#startdir#> <#destdir#> ### recursive copy
she then wants to be able to select one of these items, fill in the blank(s) and just hit enter to run the command.
Customer X is willing to specify ahead of time what commands she is likely to want to use (in windows-speak) and then hire the developer to translate those into bash commands, and then put them together in a script that allows her to talk windows-speak to bash and get back the list of commands.
NOTE: Customer X doesn't like apropos because it assumes familiarity with terms used in bash, as opposed to windows-speak. For example:
apropos shortcut
doesn't give her anything about creating symlinks (even though that is exactly what she wants) because she doesn't know what windows shortcuts are called in linux. Obviously, windows concepts don't carry over 100% so she will have to learn eventually, but she's a busy person and is asking for this as a way to "ease" her into linux understanding.
Question: What is the best way to get started on something like this? Is there a perl, python, ruby script out there that does something like this already? Is there something in bash that can simulate this kind of feature request?
What you probably want is to override bash's command-not-found handler. Here's the section in /etc/bash.bashrc in a standard Ubuntu install that installs the handler:
...
# if the command-not-found package is installed, use it
if [ -x /usr/lib/command-not-found ]; then
function command_not_found_handle {
# check because c-n-f could've been removed in the meantime
if [ -x /usr/lib/command-not-found ]; then
/usr/bin/python /usr/lib/command-not-found -- $1
return $?
else
return 127
fi
}
fi
...
In effect, if a command is not found, a user specified program is executed with that command as a parameter. In the case of Ubuntu, it's a Python program that checks to see if the command the user typed is a valid application that can be installed, and if it is, informs the user that he/she can install it.
What you probably want to do is compare it to you hashref of commands and usage strings and display the appropriate one if there's a match.

Resources