zshparameter-expansion

zsh parameter expansion puzzle : last argument of command


I need to find the last argument of a command, check if it's a valid file name, if so I pass that to fre which is part of my command-completion toolchain. The code I have works except in one case: if the last argument of the command is a valid file name containing embedded spaces. I can't see how to deal with this. Any ideas? Here's the code:

   fre_preexec () {
      local tmpcmd="$1"
      local cand_fname="${tmpcmd##* }"
      [ -f "$cand_fname" ] && fre --add "$cand_fname"
   }

According to the documentation, for preexec $1 contains the entire command string. Thanks in advance.

Edit: I managed to get something that snagged a file name, but I can't get that to past the -f test:

   local tmpcmd="$1"
   local cand_fname="${tmpcmd##*[^\\] }"
   echo "cand_fname ${cand_fname}"
#   [ -f "$cand_fname" ] && fre --add "$cand_fname"
   [ -f "$cand_fname" ] && echo fre --add "$cand_fname"

If the command is ls -l fred\ bill.txt, this prints

cand_fname fred\ bill.txt

but does not echo anything. I can't figure that part out.


Solution

  • You need a mix of the z parameter expansion flag to split a string according to normal shell rules, and the Q flag to handle removing the literal backslash (And other quote related characters) left in the arguments to preexec. For example:

    #!/usr/bin/env zsh
    
    preexec () {
        local tmpcmd="$3"
        # Note the nested expansion
        local cand_fname=${(Q)${(z)tmpcmd}[-1]}
        print "cand_fname: $cand_fname"
        [ -f "$cand_fname" ] && echo fre --add "$cand_fname"
    }
    
    
    ls -l fred\ bill.txt
    

    and in action:

    $ touch "fred bill.txt"
    $ zsh demo.sh
    cand_fname: fred bill.txt
    fre --add fred bill.txt
    -rw-r--r-- 1 x x 0 Feb  7 05:07 'fred bill.txt'