How can I repeat only a part of a command in bash?
I’d like to know if it’s possible to just repeat part of a command.
I.e. if I do ls /path/to/somewhere -a
, I only want to remove ls
and -a
.
I know that if I do !!
it repeats the previous command (appending the last command to whichever command you write before it) and that if I do !$
it includes the last part of the string, but I’d like to know if it’s possible to re-use only the e.g. middle part of the previous command.
Sure, use !^
e.g.
$ ls /path/to/somewhere -a
ls: cannot access '/path/to/somewhere': No such file or directory
$ echo !^
echo /path/to/somewhere
/path/to/somewhere
$
Alternatively (incurring an extra keystroke) you could use !:1
.
$ ls /path/to/somewhere -a
ls: cannot access '/path/to/somewhere': No such file or directory
$ echo !:1
echo /path/to/somewhere
/path/to/somewhere
$
This is fully documented in the Event Designators and Word Designators sections of the bash
man page.
Expanding on @steve’s answer to address your general request:
I’d like to know if it’s possible to re-use only the e.g. middle part of the previous command.
You can use multiple word ranges to re-arrange a prior command:
!:x
, to recover the xth word, first word is 0th!:x-y
, to recover the xth to yth word, inclusive
For example:
$ ls /foo /bar -a /baz >/dev/null 2>&1
$ echo !:1-2 !:4
echo /foo /bar /baz
/foo /bar /baz
Note that redirection operators count as individual words, so:
$ ls /foo >/dev/null 2>&1
$ echo !:3 !:2
echo /dev/null >
bash: syntax error near unexpected token `newline'
Word selection by index can be further expanded to work for commands other than the most recent:
!-n
, select the nth most recent command!string
, select the most recent command beginning withstring
Thus, combining those you can get:
$ cat /foo -vet >/dev/null 2>&1
$ ls /bar -a >/dev/null 2>&1
$ cp !cat:1 !ls:1-2 >/dev/null 2>&1
cp /foo /bar -a
I tend to think of the command history as an array – rows of commands, columns of words – which makes this kind of navigation natural.
One caution: crafting new commands by suturing parts of old ones can lead to commands having irreversible effect. I recommend placing :p
on one of the arguments to display the built command, rather than invoking it.
For example: rm !ls:1-4:p
. This will print the final rm
command, after inserting the 1st through 4th argument of the most recent ls
command.
Bash has a yank-last-arg
command for working with your command history:
yank-last-arg (M-. or M-_)
Insert last argument to the previous command (the last word of the previous history entry). With a numeric argument, behave exactly like
yank-nth-arg
. Successive calls toyank-last-arg
move back through the history list, inserting the last word (or the word specified by the argument to the first call) of each line in turn. Any numeric argument supplied to these successive calls determines the direction to move through the history. A negative argument switches the direction through the history (back or forward). The history expansion facilities are used to extract the last argument, as if the!$
history expansion had been specified.
With the default keybindings, you can activate it by pressing Meta..1 That copies the last word of your most recent command to where your cursor is.
To copy the nth word instead of the last word, press Metan, then Meta..
I find that this is easier to use and is less error-prone than history expansion, since it works interactively.
Another way to tweak a previous command interactively is to use the edit-and-execute-command
feature:
edit-and-execute-command (C-x C-e)
Invoke an editor on the current command line, and execute the result as shell commands. Bash attempts to invoke
$VISUAL
,$EDITOR
, andemacs
as the editor, in that order.
Use the up/down arrow keys to recall the command, then press CtrlX, CtrlE to open that command in your $EDITOR
. You can then edit the command to your liking, then when you save and exit the editor, it will execute.
1 Depending on your keyboard layout, Meta could mean holding down an Alt key, holding down an Option key, or pressing and releasing the Esc key. The . is the period or full-stop character, in case you have difficulty reading it here.
Arrow-up. Then backspace
to remove a few chars. Ctrl+A
to jump to start of line, DEL
to remove a few chars. Visual feedback all the way, no guessing. Ctrl+E
to jump back to end of line.