23

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.

2
  • 1
    Instead of figuring out how to get the Nth argument of the previous command, I would probably just form my command as la -a /path/to/somewhere and use $_ or !$ to get that last argument. That would work for any case that doesn't have multiple positional arguments.
    – Stewart
    Commented Jun 19, 2022 at 8:56
  • Use Alt+<digit> then Alt+. to substitute the <digit> argument.
    – GACy20
    Commented Jun 21, 2022 at 12:28

4 Answers 4

27

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.

2
  • 1
    I had forgotten about asking this question. Amazing explanation. Thanks a lot!
    – telometto
    Commented Jun 22, 2022 at 16:10
  • 1
    To repeat all arguments of the previous commands instead of only the first: !:1* Commented Jun 27, 2022 at 8:03
17

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 with string

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.

1
  • 1
    Wish I could give two solutions, because this is a very in-depth explanation. Thanks a lot for this!
    – telometto
    Commented Jun 22, 2022 at 16:12
5

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 to yank-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, and emacs 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.

2
  • 2
    If your terminal doesn't suck, bash line editing is about as good as most editors unless the line gets extremely long. So just up-arrow, ctr-left-arrow (backward word, although that stops on / as well as space), ctrl-u (kill to start of line). Or ctrl-a (start of line), meta-d (kill word) twice, once each for ls and -a. But yes, meta-. is by far the best answer for interactive use; I use it all the time for stuff like mkdir foo / cd foo. I'm surprised other people posted different answers before this with syntax that's no easier to remember, but you don't see if it worked until run. Commented Jun 21, 2022 at 4:16
  • 2
    Note that this is in emacs mode only. For vi mode, see superuser.com/questions/18498/… Commented Jun 21, 2022 at 15:10
0

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.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .