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 k
th 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 k
th command in a pipeline" Does this command exist?
2 Answers 2
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).
-
Just set
shopt -s histverify
in bash and always see the history expansion before execution (that will change readline behavior)user232326– user2323262016年10月12日 11:18:18 +00:00Commented Oct 12, 2016 at 11:18 -
In zsh the same utility is set by
HIST_VERIFY
.user232326– user2323262016年10月12日 11:45:38 +00:00Commented Oct 12, 2016 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 norbindkey '\MP' last-pipe-elements
seems to be working, any thoughts?mbigras– mbigras2016年10月13日 09:04:02 +00:00Commented Oct 13, 2016 at 9:04 -
@mbigras, Yes, that
\eP
is for terminals that sendESC 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.Stéphane Chazelas– Stéphane Chazelas2016年10月13日 09:18:11 +00:00Commented Oct 13, 2016 at 9:18
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.)
-
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– mbigras2016年10月12日 07:18:27 +00:00Commented Oct 12, 2016 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– Wildcard2016年10月12日 07:24:27 +00:00Commented Oct 12, 2016 at 7:24 -
@Wildcard thanks for that, I also found a thegeekstuff tutorial that looks pretty goodmbigras– mbigras2016年10月12日 07:43:44 +00:00Commented Oct 12, 2016 at 7:43
-
1See also
!:3*
as a shortcut for!:3-$
Stéphane Chazelas– Stéphane Chazelas2016年10月12日 07:52:29 +00:00Commented Oct 12, 2016 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).user232326– user2323262016年10月12日 11:19:56 +00:00Commented Oct 12, 2016 at 11:19
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.fc
, which can open a requested command in your favorite text editor for editing before executing it.