Bash function not working in Zsh

12,586

Solution 1

  • local is a builtin, not a keyword, so local files=(…) isn't parsed as an array assignment but as a string assignment. Write the assignment separately from the declaration. (Already found by llua, but note that you need to initialize files to the empty array or declare the variable with typeset -a, otherwise the array starts with a spurious empty element.)
  • Zsh arrays are numbered from 1, not from 0 like in bash and ksh, so ${files[0]} must be written $files[1]. Alternatively, tell zsh to behave in a way that's more compatible with ksh and bash: put emulate -L ksh at the beginning of the function.
  • Unless you go the emulate route, your _notes function will print zsh: no matches found: foo* if there is no completion for foo, because by default non-matching globs trigger an error. Add the glob qualifier N to get an empty array if there is no match, and test whether the array is empty.
  • There is another error in your _notes function which affects notes in subdirectories: you must strip away the prefix up to the completion, so that if e.g. ~/notes/foo/bar exists and you type n b<TAB>, COMPREPLY is set to contain b, not foo/b.

If you want to keep a file that's readable by both bash and zsh:

type emulate >/dev/null 2>/dev/null || alias emulate=true
function n() {
  emulate -L ksh
  local arg; typeset -a files
  for arg; do files+=( ~/".notes/$arg" ); done
  ${EDITOR:-vi} "${files[@]}" 
}

function nls() {
  tree -CR --noreport $HOME/.notes | awk '{ 
      if (NF==1) print $1; 
      else if (NF==2) print $2; 
      else if (NF==3) printf "  %s\n", $3 
    }'
}

# TAB completion for notes
function _notes() {
  emulate -L ksh
  local x files
  files=($HOME/.notes/**/"$2"*)
  [[ -e ${files[0]} ]] || return 1
  COMPREPLY=()
  for x in "${files[@]}"; do
    COMPREPLY+=("$2${x#$HOME/.notes*/$2}")
  done
}
complete -o default -F _notes n

If you want to port your code to zsh:

function n() {
  local files
  files=(${@/#/~/.notes/})
  ${EDITOR:-vi} $files
}

function nls() {
  tree -CR --noreport $HOME/.notes | awk '{ 
      if (NF==1) print $1; 
      else if (NF==2) print $2; 
      else if (NF==3) printf "  %s\n", $3 
    }'
}

# TAB completion for notes
function _notes() {
  setopt local_options bare_glob_qual
  local files
  files=(~/.notes/**/$2*(N))
  ((#files)) && COMPREPLY=($2${^files##~/.notes*/$2})
}
complete -o default -F _notes n

Solution 2

zsh's typeset(local) command can't define arrays with it's syntax. You can create arrays, but you can't also set values all in one command.

function n() {                                                         
local arg files; for arg; do files+=( ~/.notes/$arg ); done
vim ${files[@]}
}

is one way to fix it.

Share:
12,586

Related videos on Youtube

jasonwryan
Author by

jasonwryan

Updated on September 18, 2022

Comments

  • jasonwryan
    jasonwryan almost 2 years

    I have been slowly migrating from Bash to Zsh and have got to the point where everything I have moved across is working well, with one exception.

    I have a couple of functions in my .bashrc that I use dozens of times a day and two of them do not work under Zsh. The three functions comprise a basic note taking facility.

    They are currently in .config/zsh/functions:

    function n() { 
    local arg files=(); for arg; do files+=( ~/".notes/$arg" ); done
    ${EDITOR:-vi} "${files[@]}" 
    }
    
    function nls() {
    tree -CR --noreport $HOME/.notes | awk '{ 
        if (NF==1) print $1; 
        else if (NF==2) print $2; 
        else if (NF==3) printf "  %s\n", $3 
        }'
    }
    
    # TAB completion for notes
    function _notes() {
    local files=($HOME/.notes/**/"$2"*)
        [[ -e ${files[0]} ]] && COMPREPLY=( "${files[@]##~/.notes/}" )
    }
    complete -o default -F _notes n

    Which I source from .zshrc like so:

    autoload bashcompinit
    bashcompinit
    # source zshrc functions file
    source "$HOME/.config/zsh/functions"

    nls works as expected, but neither n nor Tab completion work.

    I read man zshcompsys where it says:

    The function bashcompinit provides compatibility with bash's programmable completion system. When run it will define the functions, compgen and complete which correspond to the bash builtins with the same names. It will then be possible to use completion specifications and functions written for bash.

    However, when I try Tab completion, nothing happens and when I enter n notename, Vim opens my /home in file browser mode - not quite the expected behaviour.

    All of the other functions defined work well. How do I migrate these functions to work under Zsh?

  • jasonwryan
    jasonwryan over 11 years
    Upvoted: thank you. Any suggestion as to how to get the completion script to work in zsh?
  • jasonwryan
    jasonwryan over 11 years
    Thanks Gilles. With a minor edit, the zsh-ported n() function works; but the _notes() function still fails with no relevant matches. The emulation doesn't work at all (opens the Vim file browser). Do I need some other configuration in my .zshrc? I feel, given the quality of the two answers, that I must be missing something.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    @jasonwryan I tested n and running _notes manually (I didn't test with bash completion emulation) in zsh -f, and both seem to work. Post a trace of running n and _notes with set -x.
  • jasonwryan
    jasonwryan over 11 years
    Trace file: I am not sure it is that helpful... Although it does show the array picking up all the files; the Tab completion still fails.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    @jasonwryan The trace from n looks correct, what's wrong with it? _notes looks at its second argument, so you need to test it with _notes _ irc.
  • jasonwryan
    jasonwryan over 11 years
    The n() function does work (see my first comment); it is the Tab completion that fails, with n<space>irc<tab><enter>.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    @jasonwryan There was another bug in your _notes function, and also one in my compatibility n and one in my translated _notes. See my updated answer.
  • jasonwryan
    jasonwryan over 11 years
    Thanks Gilles: that is working now. Your help is much appreciated.
  • llua
    llua over 11 years
    the n function could also read n() { $EDITOR "${@/#/.notes/}"; } and prevent a loop, while working in bash and zsh
  • yPhil
    yPhil over 8 years
    "Zsh arrays are numbered from 1, not from 0 like in bash and ksh, so ${files[0]} must be written $files[1]" Oh, the horror ;(