7

I have a script myscript which takes two arguments:

  1. hostname
  2. directory

How can I write my own zsh completion, so that whenever I do


mysript <TAB>

it completes from my hosts list (ie same as ssh does) and when I do


mysript host1 <TAB>

it completes from directories in /home/martin/test/ ?


1
  • You might take a look at This.
    – AsenM
    Dec 6, 2018 at 16:30

1 Answer 1

5
+500

Thank you for the interesting question. I would like to do the same in my scripts. The documentation is dense and not so easy to understand; I have not yet learned to work without actual options in the script. Here's my first attempt at accomplishing the goal with actual options.

First, I created a shell script named myscript.sh that uses options.

#!/usr/bin/env sh
self=$(basename "$0")
hflag=0 # Boolean: hflag is not yet detected
dflag=0 # Boolean: dflag is not yet detected

function usage() {
    echo "Usage: $self [ -h <hostname> | -d <directory> ]"
}

# If no options were given, exit with message and code.
if (($# == 0)); then
    usage
    exit 1
fi

# Process options and option arguments.
while getopts ":h:d:" option; do
    case "${option}" in
        h ) hflag=1 # The h option was used.
            host=${OPTARG} # The argument to the h option.
            ;;
        d ) dflag=1 # The d option was used.
            dir=${OPTARG} # The argument to the d option.
            ;;
        \?) # An invalid option was detected.
            usage
            exit 1
            ;;
        : ) # An option was given without an option argument.
            echo "Invalid option: $OPTARG requires an argument" 1>&2
            exit 1
            ;;
    esac
done

# One of hflag or dflag was missing.
if [ $hflag -eq 0 ] || [ $dflag -eq 0 ]; then
    usage
    exit 1
fi

# Do something with $host and $dir.
# This is where the actions of your current script should be placed.
# Here, I am just printing them.
echo "$host"
echo "$dir"

# Unset variables used in the script.
unset self
unset hflag
unset dflag

Next, I determined where zsh looks for autocomplete files.

print -rl -- $fpath

I chose one of the directories, /usr/local/share/zsh/site-functions in my case. The filenames that are considered autocomplete files begin with an underscore _ character. I created the file, _myscript, in the directory. The portion after #compdef is the actual script name, above.

#compdef myscript.sh

_myscript() {
    _arguments '-h[host]:hosts:_hosts' '-d[directory]:directories:_directories'
}

_myscript "$@"

I then executed compinit to pick up the new autocomplete definition provided by the _myscript file. The result is that I can now use tab completion to specify a host after the -h option and a directory after the -d option while still maintaining some sanity in the parsing of options and option arguments in the script itself. The tab completion presents available options even before invoking myscript.sh as well as making option order irrelevant.

Usage becomes something like the following.

myscript.sh -h <TAB> -d ~/test/<TAB>

Summary Solution

On the second attempt, I created a simple shell script, zscript.sh.

#!/usr/bin/env sh
echo "$1"
echo "$2"

And I created a file, /usr/local/share/zsh/site-functions/_zscript.

#compdef zscript.sh

_zscript() {
    _arguments '1: :->hostname' '2: :->directory'
    case $state in
    hostname)
        _hosts
    ;;
    directory)
        _directories -W $HOME/test/
    ;;
    esac
}

I executed compinit.

5
  • thank you, that works. Although not exactly as I imagined. First, as I explained, I need to complete directories form specific path, regardless what my $PWD is currently. Wherever I am in my filesystem tree, I need completion to complete directories from /foo/bar/. Second, is it possible to get rid of the -h and -d switches ? First argument should always be host, second always directory. Dec 7, 2018 at 9:00
  • completing hosts works. But completing the contents of $HOME/test/ does not work. When I press TAB, it simply completes $HOME/test/ path itself and then offers me files in current path. Also, not sure what you mean with -P vs -W. I need to complete the contents of $HOME/test/, not the whole path. If it contains dirs aa1 and aaa2, then tab should offer those two. Dec 7, 2018 at 17:16
  • perfect. Now it works. thaanks. Dec 7, 2018 at 20:02
  • @Christopher - what should I use instead of _directories -W, when I want to complete only files in given path, instead of directories? Aug 20, 2021 at 8:57
  • @400theCat Sorry for late response. I see you got the answer here: unix.stackexchange.com/questions/665578/… Aug 21, 2021 at 13:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.