^@ you're referring to is actually a newline character, so the problem you describe will happen when your visual selection includes the newline (e.g.
v$ to select until the end of the line) or when it spawns multiple lines.
Assuming your external tool you're shelling out to is able to handle multiline strings appropriately, Vim's
shellescape() will be able to handle it in a way that the shell will interpret it correctly, by turning it into a single quoted string that can embed newlines.
shellescape() (where the
\n here represents an actual newline character), and the shell is fine with it.
The problem you're having is that
:execute is now seeing two separate commands, in this case:
$read !bitwise --no-color 'multiline
Or, more geneally, for
first line\nsecond line:
$read !bitwise --no-color 'first line
There are two possible solutions to this problem. The first is to pass a non-zero second argument to
shellescape(), which will make it do some extra magic to handle cases like this one with the newline. From
With a non-zero-arg
<NL> character is also escaped. [...]
Example of use with a
:exe '!dir ' . shellescape(expand('<cfile>'), 1)
The example itself is using
: execute, so it's actually a good illustration of your issue.
So one possible solution is:
silent execute '$read !'. "bitwise --no-color ". shellescape(@@, 1)
Another, actually better solution, is to use the
system() function to shell out instead, in which case you don't need to worry about escaping the string from
In order to put the contents of the
system() result into the new buffer, you can use the
:put command together with the
@= expression register.
Putting it all together:
silent put =system('bitwise --no-color '.shellescape(@@))
You can also use
setline() instead of
:put, which makes it even easier to get rid of the empty line at the start of the buffer. With
systemlist(), to get the shell output as a list of lines:
call setline(1, systemlist('bitwise --no-color '.shellescape(@@)))