Process substitution may offer the closest syntax to what you want.
This works, and may be to your liking:
vim <(cmd1 | cmd2 | ...)
That lets you keep the text of your whole command together, and to put it after (rather than before) the command that sends it to vim
, which seem to be your main goals. The absence of a -
argument is intentional.
You don't need to define a function or alias for that, but of course you could shorten vim
to v
by defining v
as an alias or function:
alias v=vim
v() { vim "$@"; }
v <(cmd1 | cmd2 | ...)
is one more character than v 'cmd1 | cmd2 | ...'
. But it also avoids the quoting hell that muru cautions about. '
, "
, and \
can appear inside <(
)
and they work in the usual way. This also nests fine: you can have other parentheses in your command, so long as they are used in a manner that is otherwise syntactically correct.
This is process substitution. Bash creates a pipe that is accessible with a path like /dev/fd/63
(not to be confused with the pipes involved in your command itself). It substitutes the path of that pipe for <(cmd1 | cmd2 | ...)
, so vim
sees a filename like /dev/fd/63
. Your command, cmd1 | cmd2 | ...
, is run asynchronously in a subshell, and its output is sent to the pipe, which vim
reads. (That's why you don't write -
: vim
reads from /dev/fd/63
or whatever it ends up being called, not from standard input.)
muru has pointed out that you can write an alias that expands to vim <(
:
alias v='vim <('
You can put this alias definition in ~/.bash_aliases
or at the end of ~/.bashrc
.
That gets you even closer to the syntax you originally wanted--you just have to write a )
at the end of your command:
v cmd1 | cmd2 | cmd3)
Process substitution has the additional benefit that you can use it multiple times, writing multiple <(
)
constructions in the same command, in case you want to run multiple commands separately and view their output in separate vim
buffers.
vim <(cmd1 | cmd2 | cmd3) <(cmd4 <infile) <(FOO=bar cmd5) <(cmd6 | cmd7)
Of course, as muru says, postfix operations are still simpler for the use case of running a pipeline and opening its output in vim
, as you need only tack on another command for vim
at the end of your pipeline.
Thanks to muru for the insight that v
can be made an alias for vim <(
.
The simplest cases: a better way to implement your original v
vim <(
)
, detailed above, can be used even in the simplest cases.
But it's syntactic overkill when you're not writing a pipeline of two or more commands (cmd1 | cmd2
), applying redirections to your command (cmd <infile
), or assigning environment variables for the duration or your command (FOO=bar cmd
). So you may still want to have a function that does what your original v
function did.
Your implementation runs a loop to concatenate the text of the arguments. This is complicated, and also breaks in most scenarios involving quoting, even if you fix $1
to "$1"
to prevent initial splitting and globbing. In v 'foo bar'
, your v
function (like any function) doesn't receive any quotes: it sees foo bar
. That's fine, since it receives it as a single argument... until it constructs a script that contains it and runs that script, at which point foo bar
gets parsed as two words, which become two arguments.
Fortunately, there's a more reliable way, which is also simpler and shorter. "$@"
expands to all the arguments passed to the current function, or to the current script, if not in a function. Separate arguments are neither further split nor joined with one another. So all you need is:
v() { "$@" | vim -; }
(Of course, if you're defining v
to do something else--perhaps as an alias for vim <(
as described above--then you'll want to call this something else, perhaps u
.)
Running that command defines a shell function v
that runs the command named in its first argument and passes its subsequent arguments. This does not construct a separate script, and it does not use bash -c
or eval
. So it does not risk splitting or joining arguments incorrectly, because arguments are never split or joined: they're separate going into the function, and "$@"
uses them separately.
You can put that at the end of ~/.bashrc
so it will be defined for your interactive shells.
Or if you prefer it be a script, then make a file called v
(or whatever you want the command name to be) with the contents:
#!/bin/bash
"$@" | vim -
Mark the file executable (chmod +x v
) and put it in a directory listed in your $PATH
. I suggest ~/bin
, which is automatically added to your $PATH
when you log in, if it exists (unless you've changed ~/.profile
to not do that).
Best Answer
You can grep the output if you pipe stderr as well, e.g.:
From Pipelines section of GNU Bash manual:
However, this will not work for the progress line: CLI commands usually test whether the output is to a terminal and discard updated output if it isn’t. We need a hacky workaround to get around that. First redirect the full output to a file:
This blocks the current terminal and you can not just send it to the background because then it discards the progress line again. So to get this line, open a second terminal in the same directory and process the file, e.g.:
The advantage of using
tail
is that you also get theAborted.
line when theplay
exited: