3

I have the following pipeline:

➜  echo ,cats,and,dogs, | sed -e 's/,[^,]*,[^,]*,/,,,/'
,,,dogs,

I know that I could run a command like !! to "run the last command" or !:1 to "get the last arguments" but I'm wondering is there some command that I can run that will let me "get the kth command+args from a pipeline"

So in this example if I wanted to pipe some other output into the sed utility I could do something like this right after running the above pipeline:

$ echo ,foo,bar,baz, | %:2

where %:2 is some maybe-fictional command that I don't know, that "runs the kth command in a pipeline" Does this command exist?

4
  • Why the bash tag if you're using zsh? There are plenty ways to address it better than with history expansion (IMO, I'm not a big fan of history expansion) with zsh. Oct 12 '16 at 7:56
  • Just updated tags! What other more zsh specific ways are you thinking of?
    – mbigras
    Oct 12 '16 at 8:10
  • For instance a widget with numerical argument bound to some key. So that you'd type Alt+2 Alt+Shif+P for instance to insert the last 2 commands from the last pipeline, similar to Alt+_ Oct 12 '16 at 8:30
  • You might just want to use fc, which can open a requested command in your favorite text editor for editing before executing it.
    – chepner
    Oct 13 '16 at 2:04
3

History expansion was great in the 80s (when introduced by csh) when terminals were very slow and limited. The problem though is that they're not WYSIWYG. You don't see what it expands to until it's too late (the command is already started) (though some shell allow to perform the expansion before pressing Enter).

Here, I'd create a widget that inserts the last pipeline element at the cursor. With zsh (add it to ~/.zshrc):

last-pipe-elements() {
  emulate -L zsh
  if [[ $WIDGET = $LASTWIDGET ]]; then
    # Invoking the widget several times retrieves pipeline
    # elements for older command lines
    ((last_pipe_elements_iteration++))
    LBUFFER=$last_pipe_elements_saved_LBUFFER
  else
    last_pipe_elements_iteration=1
  fi
  last_pipe_elements_saved_LBUFFER=$LBUFFER

  local -a words
  words=(${(z)${history[@]}[last_pipe_elements_iteration]})

  local nth_pipe=${words[(In:NUMERIC:)\|]}
  ((nth_pipe)) || return

  LBUFFER+=${(j: :)words[nth_pipe,-1]}
}
zle -N last-pipe-elements
bindkey '\eP' last-pipe-elements

That way, you enter Alt+Shift+P (here assumed to send the ESC and P character sequence, you may need to adapt it for your terminal that may send \xd0 ($'\MP') or even \xf0 ($'\Mp') or something completely different, check with Ctrl+V) to insert the last pipeline element (including the |) of the last command, press it again for the command before that, and use Alt+2Alt+Shift+P for the last 2 pipe elements instead.

(z) invokes the shell parser to split the command lines into words. We find the | we want and then join the words on the right of that with one space. That means that |tr a b will become | tr a b, but that shouldn't affect the meaning of the command as long as you don't use complex multi-line constructs (sed 's/ /x/' will not be changed to sed 's/ /x/' for instance).

If you don't want to define a new widget, another thing you can do is search back (with Ctrl+R assuming emacs mode, though you can do the equivalent in vi mode) for the previous |, delete to the end of the line with Ctrl+K and get back to your command (Down) to paste it with Ctrl+Y. That would still be quicker than using history expansion and would still be visual (you can better visually select which | you want to cut from).

4
  • Just set shopt -s histverify in bash and always see the history expansion before execution (that will change readline behavior)
    – IsaaC
    Oct 12 '16 at 11:18
  • In zsh the same utility is set by HIST_VERIFY.
    – IsaaC
    Oct 12 '16 at 11:45
  • Thanks you @Stephane! I'm having trouble getting my bindkey to work. I'm using OSX 10.11 which I think may be the problem I'm prety sure \M is the "option" or "meta" key but neither your version nor bindkey '\MP' last-pipe-elements seems to be working, any thoughts?
    – mbigras
    Oct 13 '16 at 9:04
  • @mbigras, Yes, that \eP is for terminals that send ESC P upon Alt+Shift+P. Check what character or character sequence your terminal sends. It might be that it sends the same as for Alt+P (one of the most useful widgets in tcsh/zsh) in which case you may want to use a different key or key combination. Oct 13 '16 at 9:18
3

Don't know how to get "after the pipe" but "after the third white space" may help:

$ echo ,cats,and,dogs, | sed -e 's/,[^,]*,[^,]*,/,,,/'
,,,dogs,
$ echo ,cat,and,mouse, | !:3-$
,,,mouse,

Workaround for "after pipe":

$ echo ,cats,and,dogs, | sed -e 's/,[^,]*,[^,]*,/,,,/'
,,,dogs,
$ after_pipe="$(cut -d "|" -f 2- <<<"!!" )"
$ echo ,cat,and,mouse, | eval "$after_pipe"
$ ,,,mouse,

(cut would need to be something better if there are quoted pipes "|" in the first command.)

6
  • wow, this seems like a great option so far. Kind of a bummer because by default my pipes get|stuck|togeher|like|this but I think it's a setting that oh-my-zsh did that I'm sure I could change. What is !:3-$ called? I've been googling around for bash history expansion but I couldn't find.
    – mbigras
    Oct 12 '16 at 7:18
  • 1
    @mbigras, it's still called history expansion. You won't find it online very much because it's not well known. Look at the man page instead: LESS='+/^HISTORY EXPANSION' man bash
    – Wildcard
    Oct 12 '16 at 7:24
  • @Wildcard thanks for that, I also found a thegeekstuff tutorial that looks pretty good
    – mbigras
    Oct 12 '16 at 7:43
  • 1
    See also !:3* as a shortcut for !:3-$ Oct 12 '16 at 7:52
  • It is a good idea to set shopt -s histverify in bash to always see the history expansion before execution (that will change readline behavior).
    – IsaaC
    Oct 12 '16 at 11:19

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.