Suppose I have mapped the following function:

noremap <C-F12> :! bash -c "\
  git rev-parse --show-toplevel \
| xargs -I {} \
  ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q {}" \

No matter how I nest it, it seems as if VIM will not run the function. I suspect a possible solution in the form of setting vim's shell to "/usr/bin/env bash" instead of sh, somehow.

But maybe there is some other solution? (or just a typo...?)

The goal here is to run the command exactly as if it were BASH and not Vim.


In Vim line continuation is done with a backslash on the new line. Like:

noremap <C-F12> :!bash -c '
            \git rev-parse --show-toplevel
            \\| xargs -I {}
            \ ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q {}'

Another point is the usage of | in the command line, as it is a command separator for Vim (like ';' for the shell). So it has to be escaped. See the \\| above? The first \ is for line continuation, the second to escape the |.

See :help line-continuation

Anyway, I think your mapping could be more vimish:

noremap <C-F12> :call system('ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q '
            \. shellescape(system('git rev-parse --show-toplevel'))) <CR>

This uses two Vim functions:

  • system() to execute a shell command and get the output
  • shellescape() to escape a file name for usage in a shell command.

See :help system() and :help shellescape().

| improve this answer | |
  • 2
    Or dump the shell commands in a script and call that – D. Ben Knoble Jun 27 at 1:36
  • regarding the comment about newlines, are you saying that when I write this remapping command in the .vimrc file, that the escapes do not actually espace the \n in the file when the file is parsed by the lisp lexer? I say this because the literal file string would be: "foo bar\n\whatever", which actually back-slashes the w and would be un-parsable without two passes or always looking ahead to see if the next char is a slash...if that is the case, that is interesting – Chris Jun 27 at 17:26
  • @Christopher "Lisp Lexer"? – Ralf Jun 27 at 19:48
  • Oh, no you are right, I am getting it confused with Emacs now. Either way, VimLangauge or whatever they call it has a parser, a lexer, etc. en.wikipedia.org/wiki/Lexical_analysis here, you can read all about a waste of time. Anyhow, processing the backslashes after the newline suggests they used some unorthodox approaches at the bottom level of whatever it is that creates the "vim-keymap and interface" (the only thing we users care about). And all I care about is recording new keymaps in a way that is readable by me. – Chris Jun 28 at 16:11

Minimal Verifiable Solution

| is a vim character, and must be escaped.

noremap ~~ :! 
\bash -c "git rev-parse --show-toplevel
\\| xargs -I {} 
\ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q {}"<CR>

Note: the following will work as well

noremap ~~ :! 
\bash -c "git rev-parse --show-toplevel \
\| xargs -I {} 
\ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q {}"<CR>

And this will fail:

noremap ~~ :! 
\bash -c "git rev-parse --show-toplevel \
\| xargs -I {} \ 
ctags -R --sort=yes --c++-kinds=+p --fields=+iaS --extra=+q {}"<CR>

This seems to be a bug.

Style Guide

The xargs -I {} trick is an excellent way to maintain full control of the arguement, with lateral linux-distribution compatibility. The purpose of xargs is primarily to maintain feed-forward semantics so that direction of control-flow does not hop back and forth for the downstream reader (probably yourself) unnecessarily, i.e:

grep 'foo' <(cat bar)
sed 's/foo/bar/' $(generate baz)

Further, xargs -I {} is semantically equivalent to parallel --keep-order -I {}. gnu-parallel advertises compatibility with xargs; however, in my experience, this is superficial only; -I {}, however, removes the impedement.

Contextual Discussion

When writing BASH, it is preferable to use double quotes and reserve single quotes for nested languages such as awk, sed, grep, perl, etc. This reduces clutter, and maintains env variable substitution. The sub-languages often have a mechanism for nested environment variable substitution through the use of flag-arguments.

An example is awk, which uses -v varname=${BASH_ENV_VARIABLE}, or python, which uses -E to turn on environment variable substitution within the single quotes. All instructions for these sub languages, which can be used from within vim, are accessible via info or man. man is available in all distributions I am aware of, and can be used as follows:

man <command-name>

# e.g.
man python

Further, vi can be connected to the command line vi set -o vi, and accessed on the fly by typing <esc>-v. Context switching may be painful if you also switch to vimmish commands and system calls.

| improve this answer | |
  • Your "Minimal Verifiable Solution" does not work for me. I get errors like E492: Not an editor command: bash -c \\ . I tested Vim 8.2.869 and NVIm v0.5.0-577-g48ac77a14 on Linux. – Ralf Jun 27 at 19:48
  • The double backslash in the above comment is a typo. Should be single. – Ralf Jun 27 at 21:39
  • @Ralf should work now. – Chris Jun 28 at 13:59
  • @Ralf it turns out that you can put the first slash in the \\| escape sequence at the end of the prior line. That should have implied that it could be used at the end of the prior line for all lines, but it did not. That seems like a bug. In my code, I have the remapping on one line; the original formatting was only for stack overflow. So, tbh, it was content that I really didn't care about, but, as always, spent the most time on in stack exchange. – Chris Jun 28 at 14:00
  • 1
    Nice find. This trailing backslash looks really strange. – Ralf Jun 29 at 4:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.