3

There's a nice method in zsh called hash -d with example usage:

hash -d magic=/very/long/name/$USER/even/more

Then I can cd ~magic/deeper. Is it possible to achieve the same result but so that ~magic depends on the result of some script? (it's dynamic) Like so:

hash -d magic=/very/long/name/$(compute the subdirectory here)/even/more

This does not work for me because that computation is done once and not every time magic is used.

There is a workaround to create some command to do this mycd which parses the arguments and checks if the first arguments starts with ~magic and then does the actual cd. I'm not satisfied with it because it would not work with vim ~magic/something or other commands.

1 Answer 1

3

zsh does indeed support 'dynamcic directory hashes', they are called dynamic named directories (see zshexpn(1) for details). The name of a dynamic directory is given in unquoted square brackets: ~[name].

They are configured by defining the function zsh_directory_name or by creating an array called zsh_directory_name_functions containing function names. These functions are used in three ways, which are defined by the first parameter:

  1. checking if the current directory is a named directory (d)
  2. map a name to a directory (n)
  3. provide a list of names for completion (c)

Not every mode needs to be implemented, but should return a non-zero status.

Here is an example which dynamically names any /very/long/name/$USER/even/more with ~[magic:$USER] (based on the example from zshexpn(1))

zsh_directory_name() {
    emulate -L zsh
    setopt extendedglob
    local -a match mbegin mend
    local pp1=/very/long/name
    local pp2=even/more
    if [[ $1 = d ]]; then
        if [[ $2 = (#b)($pp1/)([^/]##)(/$pp2)* ]]; then
            typeset -ga reply
            reply=(magic:$match[2] $(( ${#match[1]} + ${#match[2]} + ${#match[3]} )) )
        else
            return 1
        fi
    elif [[ $1 = n ]]; then
        [[ $2 != (#b)magic:(?*) ]] && return 1
        typeset -ga reply
        reply=($pp1/$match[1]/$pp2)
    elif [[ $1 = c ]]; then
        local expl
        local -a dirs
        dirs=($pp1/*/$pp2)
        for (( i=1; i<=$#dirs; i++ )); do
            dirs[$i]=magic:${${dirs[$i]#$pp1/}%/$pp2}
        done
        _wanted dynamic-dirs expl 'user specific directory' compadd -S\] -a dirs
        return
    else
        return 1
    fi
    return 0
}

Usage:

% ls /very/long/name
bar baz foo
% ls -d /very/long/name/*/even/more
/very/long/name/bar/even/more /very/long/name/foo/even/more
% cd ~[magic:foo]
% pwd
/very/long/name/foo/even/more
% cd ~[mag  #TAB
user specific directory:
magic:bar magic:foo
% cd ~[magic:baz]
cd: no such file or directory: /very/long/name/baz/even/more

The directory /very/long/name/baz does not contain the subdirectory even/more and is thus not named.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .