17

Is there any way to grep interactively, meaning that I have a bunch of lines coming from somewhere that I want to pipe to further processing, with the option to manually specify the lines I want?

Consider this example:

#Name   Food    Music
Adam    Cake    Rock
Bert    Fruit   Folk
Caesar  Cake    Rock
Doris   Cake    Folk
Emil    Fruit   Rock
Francis Fruit   Folk
Gertrud Cake    Rock
...

I can take this file and filter it using lines like

cat people.txt | grep -v "^#" | grep "^.*Fruit.*Folk"

...to get all people to invite to a veggie Folk party. When I automate this further:

cat people.txt | grep -v "^#" | grep "^.*Fruit.*Folk" | ??? | peopleInviterScript

...I want to have some ??? in the pipe that gives me the option to select/unselect people before the invitations are sent.

Is there already such a tool in bash/zsh I just do not know about?

21

While I like the vipe approach, I've found an even cooler way: fzf.

cat people.txt | grep -v "^#" | grep "^.*Fruit.*Folk" | fzf -m | cat

...gives a nice list where I can search in and toggle the items with the keyboard and disappears completely when I am done.

  • I don't think cat does anything at the end of the pipeline – wjandrea Jul 16 at 19:50
  • 8
    @wjandrea I've never used fzf, but the use of cat is probably so fzf's stdout is a pipe instead of a terminal and so changes its behavior accordingly. – JoL Jul 16 at 21:56
  • @wjandrea, @JoL: That is correct, cat is just a replacement for any further processing as I think noone has that peopleInviterScript. :) – Bowi Jul 17 at 7:44
18

moreutils has vipe, which just runs your regular text editor (vi or something) as part of the pipeline. Any lines that you don't delete will get written back to stdout. (In general, though, you get the same result by redirecting to a temporary file and editing that file.)

5

You can also use gvim (and vim) for this. Insert gvim /dev/stdin into your pipe, and write by doing :wq! /dev/stdout when you're finished editing.

Calling gvim as the following lets you more naturally write and close with ZZ:

gvim +'nmap ZZ :wq!<cr> | r /dev/stdin | 1d | file /dev/stdout'

You can make an alias or a function of that:

gvimp() {
  gvim +'
    nmap ZZ :wq!<cr>
    r /dev/stdin
    1d
    file /dev/stdout
  '
}

Here's an example where I deleted lines 4-7 and finished by doing ZZ in normal mode:

$ seq 1 10 | gvimp | sed -r 's/^|$/=/g'
=1=
=2=
=3=
=8=
=9=
=10=

EDIT: Regular vim is a little more tricky, because it uses stdin for keyboard input and stdout for drawing the user interface. However, you can still use it by adding an extra process. You redirect vim's std* to the terminal, and use the parent process' std* for the data pipe.

vimp() {
  bash -c '
    terminal="/dev/$(ps -o tty= $$)"
    tmp_out="$(mktemp)"
    trap "rm \$tmp_out" EXIT
    vim <(cat) < "$terminal" &> "$terminal" +"
      file $tmp_out
      set noreadonly
      nmap ZZ :wq!<cr>
    "
    cat "$tmp_out"
  '
}

The temporary file is necessary because otherwise the pipe output is going to get mixed with vim's user interface and might disappear with it when vim exits.

  • 1
    Wouldn't simple vim do, without GUI? – Ruslan Jul 16 at 11:00
  • 1
    @Ruslan No, because vim needs stdin for keyboard input and stdout for drawing the user interface. – JoL Jul 16 at 15:05
  • @Ruslan I thought of a workaround for that problem and included how to use vim for that in the answer. – JoL Jul 16 at 15:33

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.