Zsh possibly adding quotes to variable value (works in bash though)

quotingzsh

I'm fairly new to zsh (shifted yesterday from bash)

I had a bash function in bash like

vl() { cmd=`echo $1 | sed -r 's/(.+):([0-9]+).+/\1 +\2/g'`; vim $cmd }

This basically converts a argument like:

vl ./manifests/production/test.pp:387:foobar  # A output of $grep -iHn test 

to

vim ./manifests/production/test.pp +387   #Aim is to open file at line-number 387

The same function in zsh doesn't work as expected. It opens a file named ./manifests/production/test.pp +387 in vim instead of opening a file named ./manifests/production/test.pp with +387 appended to it. Seems like, it's adding the quotes to $cmd.

It would be great if someone explains what's happening here. Thanks

Best Answer

In normal Bourne-style shells such as the Bourne shell, dash, ksh and bash, the syntax $variable means “take the value of the variable, split it into separate words where characters from IFS appear, and treat each word as a file name wildcard pattern and expand it if it matches one of more file”. If variable is an array, this happens to the first element of the array and the other elements are ignored.

In zsh, the syntax $variable means “take the value of the variable, except remove it if it's empty”. If variable is an array, this happens to all the elements of the array. Zsh enthusiasts consider the zsh way superior.

In zsh, you can write $=variable to perform word splitting. This isn't the best way to do what you're doing, though. Your bash function doesn't cope with whitespace in file names, and using $=variable in zsh wouldn't either. Here's a different way of parsing the argument that works in both bash and zsh and copes with any character except : in a file name. If the argument contains two colons, then everything after the first colon is removed, and the part between the first colon and the second colon is appended as a separate argument preceded by a + sign. It's slightly longer than your code, but less hard to understand, and it doesn't choke at the first hint of a space in a file name.

vl () {
  local suffix
  case $1 in
    *:*:*) suffix=${1#*:};; set -- "${1%%:*}" "+${suffix%%:*}";;
  esac
  vim "$@"
}
Related Question