How do I enable bash tab complete from a different path? - bash

I want to enable bash tab-complete to look for directories, but not in the current directory.
So for instance, if I do:
$ ls $P
dirs/ are/ here/
$ cd /not/the/P/path
$ ls
other/ stuff/
$ myProg <tab>
dirs/ are/ here
This changes the usual behavior, where I would normally see files in the current directory.
Due diligence: The best I could come up with is:
_myProg ()
{
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
if [ "${P}x" = "x" ]; then
return 1
fi
case "$cur" in
*)
pth=${P}/$( echo $cur | egrep -o "^.*/[^/]*$" )
COMPREPLY=( $( compgen -W "$( cd $pth && ls -1d "$cur"* 2>/dev/null -- "$cur" )" ) )
;;
esac
return 0
}
complete -o nospace -F _myProg myProg
which initially shows directories, but doesnt let me drill down into the directories how I want (like ls works).

Is $CDPATH helpful for you? See Advanced Bash Scripting Guide.

_myProg()
{
COMPREPLY=($(cd $P; compgen -f $2))
}
complete -onospace -F_myProg myProg

Related

How to autocomplete files under specific directory?

I created a command memo as follows:
memo() {
vi $HOME/memo/$1
}
I want to apply bash-completion to my memo to open files that is already in $HOME/memo directory:
$ memo [TAB] # to show files in $HOME/memo
$HOME/memo contains directory, so listing the file under memo is not sufficient.
In other words, I want to apply what is used in ls command in $HOME/memo to memo:
$ ls [TAB]
foo.md bar/
I tried the below but it doesn't work for nested directories:
_memo() {
local cur
local files
_get_comp_words_by_ref -n : cur
files=$(ls $MEMODIR)
COMPREPLY=( $(compgen -W "${files}" -- "${cur}") )
}
complete -F _memo memo
MEMODIR=$HOME/memo
Here's a simple example:
_memo()
{
local MEMO_DIR=$HOME/memo
local cmd=$1 cur=$2 pre=$3
local arr i file
arr=( $( cd "$MEMO_DIR" && compgen -f -- "$cur" ) )
COMPREPLY=()
for ((i = 0; i < ${#arr[#]}; ++i)); do
file=${arr[i]}
if [[ -d $MEMO_DIR/$file ]]; then
file=$file/
fi
COMPREPLY[i]=$file
done
}
complete -F _memo -o nospace memo

customize bash autocompletion

How to customize bash autocomplete to list the files in another directory for only one script option (-seq), for other script options (-speed, -define) default autocomplete is O.K. this is what I have
export files=`ls /home/tests/`
echo $files #debug
_xtest () { .
local cur
COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]}
#case "$cur" in
COMPREPLY=( $(compgen -W "${files}" -- ${cur}) )
# esac
return 0
}
complete -F _xtest -o filenames xtest
When I try to run from shell I get the below message before the list of files
> xtest -seq [TAB][TAB]
bash: .: filename argument required
.: usage: . filename [arguments]
Is there a way not to receive this message before the file list ?
How to enable default bash completion for other options ?
should use $prev instead of $cur :
_xtest () {
local prev
COMPREPLY=()
prev=${COMP_WORDS[COMP_CWORD-1]}
case "$prev" in
-seq )
COMPREPLY=( $(compgen -W "${files}" -- ${cur}) ) ;;
esac
return 0
}
complete -F _xtest -o filenames xtest

Generate an arbitrary number of process substitutions as tee arguments

I'm using this code in a bash script. I use it to transfer a source folder to multiple destinations:
cd /Volumes/ ; tar cf - SOURCE/ | tee \
>( cd /Volumes/dest1 ; tar xf - ) \
>( cd /Volumes/dest2 ; tar xf - ) \
> /dev/null
This command works well. I want to set the destinations at the beginning of the script. So the number of destinations can vary.
For example the destinations can be in a var or an array:
destinationList=/Volumes/dest1 /Volumes/dest2
cd /Volumes/Untitled/ ; tar cf - SOURCE/ | tee \
# for item in destinationList
# do
# add this code ">( cd $item ; tar xf - )"
# done
> /dev/null
Is there a nice way to do it?
This is a case where eval is one of the easier options, though it needs to be very used very carefully.
unpackInDestinations() {
local dest currArg='' evalStr=''
for dest; do
printf -v currArg '>(cd %q && exec tar xf -)' "$dest"
evalStr+=" $currArg"
done
eval "tee $evalStr >/dev/null"
}
tar cf - SOURCE/ | unpackInDestinations /Volumes/dest{1,2}
Less efficiently (but without, perhaps, causing anyone trying to audit the code's security as much consternation), one can also write a recursive function:
unpackInDestinations() {
local dest
if (( $# == 0 )); then
cat >/dev/null
elif (( $# == 1 )); then
cd "$1" && tar xf -
else
dest=$1; shift
tee >(cd "$dest" && exec tar xf -) | unpackInDestinations "$#"
fi
}
The number of tees this creates varies with the number of arguments, so it's substantially less efficient than the hand-written code or the eval-based equivalent to same.
If you only need to support new versions of bash (the below requires at least 4.1), there's some additional magic available that can provide the best of both worlds:
unpackInDestinations() {
local -a dest_fds=( ) args=( )
local arg fd_num retval
# open a file descriptor for each argument
for arg; do
exec {fd_num}> >(cd "$arg" && exec tar xf -)
dest_fds+=( "$fd_num" )
args+=( "/dev/fd/$fd_num" )
done
tee "${args[#]}" >/dev/null; retval=$?
# close the FDs
for fd_num in "${dest_fds[#]}"; do
exec {fd_num}>&-
done
# and return the exit status we got from tee
return "$retval"
}

How to make bash completion ignore certain directories?

I am trying to write a completion function that ignores file names ending with *.bak and also directories named backup. Apart from this it should work like the default Readline completion (for example the one used for the ls command). Here is my first attempt:
$ cat setup.sh
_compTest() {
local cur
FIGNORE='bak:backup'
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
COMPREPLY=( $(compgen -f "$cur") )
}
complete -F _compTest aaa
After sourcing:
$ . setup.sh
I can type
$ aaa <tab><tab>
and I get a completion list excluding *.bak files, but unfortunately not backup directories.
How can I extend this function to exclude also backup directories from the completion list?
This can be done by post processing the array returned by compgen:
_compTest() {
local cur words
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
words=( $(compgen -f "$cur") )
COMPREPLY=()
for val in "${words[#]}" ; do
name=$(basename "$val")
if [[ $name == *.bak || $name == "backup" ]] ; then
continue
fi
COMPREPLY+=( "$val" )
done
}

custom directory completion appends whitespace

I have the following directory structure:
/home/tichy/xxx/yyy/aaa
/home/tichy/xxx/yyy/aab
/home/tichy/xxx/yyy/aac
I would like to enter cdw y<TAB> and get cdw yyy/<CURSOR> as a result, so I could add cdw yyy/a<TAB> and get cdw yyy/aa<CURSOR>
The solution I came up with gives me the following:
cdw y<TAB> => cdw yyy<SPACE><CURSOR>
Following code I have so far:
_cdw () {
local cur prev dirs
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=($(compgen -d -- /home/tichy/xxx/${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
# no difference, a bit more logical:
dirs=$(compgen -o nospace -d /home/tichy/xxx/${cur}|perl -pe 's/{^/home/tichy/xxx/}{}')
COMPREPLY=($(compgen -d -W ${dir} ${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
return 0
}
complete -F _cdw cdw
cdw () {
cd /home/tichy/xxx/$#
}
Any ideas what's wrong? It seems to me that the completion process seems to be finished and isn't expecting any more input.
The simplest solution I've found so far is to generate completions that look like this:
COMPREPLY=( $( compgen -W "file1 file2 file3 dir1/ dir2/ dir3/" ) )
and add this line just before returning
[[ $COMPREPLY == */ ]] && compopt -o nospace
This sets the nospace option whenever the completion may fill in until the slash so that the user ends up with:
cmd dir1/<cursorhere>
instead of:
cmd dir1/ <cursorhere>
and it doesn't set the nospace option whenever the completion may fill in until a full filename so that the user ends up with:
cmd file1 <cursorhere>
instead of:
cmd file1<cursorhere>
If I understand correctly, you want to bash-autocomplete a directory name, and not have the extra space? (That's what I was looking for when I got to this page).
If so, when you register the completion function, use "-o nospace".
complete -o nospace -F _cdw cdw
I don't know if nospace works on compgen.
How about something like this:
COMPREPLY=( $(cdw; compgen -W "$(for d in ${cur}* ${cur}*/*; do [[ -d "$d" ]] && echo $d/; done)" -- ${cur}) )
(I'm not sure if you can call your shell function from here or not, otherwise you may have to duplicate it a bit.)
This also gets rid of your perl hack :-)
completion provides a solution for this without any workaround: funciton _filedir defined in /etc/bash_completion:
626 # This function performs file and directory completion. It's better than
627 # simply using 'compgen -f', because it honours spaces in filenames.
628 # #param $1 If `-d', complete only on directories. Otherwise filter/pick only
629 # completions with `.$1' and the uppercase version of it as file
630 # extension.
631 #
632 _filedir()
Then, specifying the following is enough:
_cdw () {
local cur prev dirs
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
_filedir # add -d at the end to complete only dirs, no files
return 0
}

Resources