4

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

1

2 Answers 2

8

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 "$@"
}
2

It's a tricky one :) One workaround is to define the function like this:

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

Note that the workaround is the use of eval, not the other minor modifications that I had to do because I lack the required stackexchange editing-fu 8-)

3
  • But, can you explain as to why is this happening? Without this, I'm clueless. Oct 14, 2013 at 10:50
  • 1
    Because by default zsh doesn't perform word splitting after parameter expansion. In bash using unquoted expansions are considered bad practice due to wordsplitting. In bash your function "works" due to wordsplitting, but can easily break should a file with whitespace is given to it.
    – llua
    Oct 14, 2013 at 12:54
  • 1
    To force wordsplitting in zsh, do vim $=cmd. Making cmd into an array would also force wordsplitting: cmd=( $(echo ...) ); vim $cmd.
    – Matt
    Oct 14, 2013 at 20:32

You must log in to answer this question.

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