Calling external scripts in the shell in vimscript

Some of my vimscripts need to call external commands, through the shell CLI. I learned some quirks the hard way, and documenting them here

The basics

Calling an external command is done through system('external-command'). If you need to pass arguments you must wrap them in shellescape(myArgument) or it will mess up spaces and quotes.

The quirks

If you echom the result and see it through :messages, you might see some ^@ weird characters. Those are how vim displays new lines, but not how they are encoded in the result.

To split the result on new lines, you should run split(myResult, "\n") and not split(myResult, '^@').

Array (List) functions in vimscript

Vimscript Arrays are called Lists. They have a set of rules and functions that I'll document here for my own reference:

Basics

  • They are defined with let myArray=['one', 'two', 'three']
  • They are zero-indexed: echo myArray[0] is one
  • They can be accessed from the end echo myArray[-1] is three

Functions

  • add(myArray, 'four') adds a new elements. myArray+=['four'] also works
  • get(myArray, 0, 'default value') reads a value, with a fallback
  • len(myArray) returns 3
  • index(myArray, 'three') returns 2 (or -1 if not found)
  • join(myArray, '/') returns one/two/three
  • split('one/two/three', '/') creates ['one', 'two', 'three']

Source

Parsing string as arguments in zsh

I had a commandOptions string variable that I wanted to use as arguments to fzf. But commandOptions had all kind of spaces and quoted strings in it and I had a hard time passing it as a set of distinct arguments and not one long string argument.

The best solution I found was to actually display this commandOptions in a multiline format, where each line was an option. Then, using the ${(f)commandOptions} modifier to read it.

get_options

#!/usr/bin/env zsh
local commandOptions=()
commandOptions+="--disabled"
commandOptions+="--delimiter=   "
commandOptions+="--with-nth=3"
commandOptions+="--bind=change:reload:sleep 0.1;${sourceBinary} {q} || true"

for line in $commandOptions; do
  echo $line
done

use_options

#!/usr/bin/env zsh
local myOptions="$(get_options)"
local selection="$(fzf ${(f)myOptions})"

Deduplicating array values in zsh

Given an array with a lot of values, I want to keep only one occurence of each value. I want to make those values unique.

The (u) modifier

local myArray=(a b c d a b b)
echo ${(u)myArray} # a b c d

Applied on an array, the (u) (for unique) deduplicates the array.

The typeset -aU definition

Somehow, I didn't work in my case and I'm still unsure why, so I found another way.

By defining my variable as an array with unique values from the get go, I don't need to deduplicate it manually, it will automatically refuse duplicate values.

typeset -aU myArray
local myArray=(a b c d a b b)
echo ${(u)myArray} # a b c d

Replacing new lines with spaces in zsh

Another one of those things I need to often do in zsh and never remember the syntax.

I had a multiline string (as returned by fzf) and wanted to convert it into a single line, with spaces instead of spaces.

This was the right formula: selection=("${(f)selection}")