zsh parameter expansion puzzle : last argument of command

53 Views Asked by At

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.

2

There are 2 best solutions below

1
Shawn On BEST ANSWER

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'
6
user1934428 On

Since $* contains all arguments, you could do a

${*: -1}

The space after the colon is crucial; otherwise :- would be parsed as a single operator.

UPDATE

This accesses the last command of the function we are in. The question addresses the problem, that inside preexec, the last argument of the command going to be executed should be displayed. I don't think that this is possible - at least not with preexec. The preexec function receives the command as a printable string. For instance, if we run a command

"foo bar" ${(z)$(baz)}"bam bom"

preexec would see this line as a string, but to understand that the last argument to foo bar is the stdout of running the command baz catenated to the string bam bom, we need first to parse the command line according to the zsh syntax (i.e. interpretation of quotes), but without actually executing baz know its output. Without executing baz we can (because of the (z) flag) not even know whether the whole output of baz will be part of the last argument.