19

I wonder if it is possible to make chain actions in Ubuntu terminal like:

action 1 on objectA . then action 2 on objectA 

without having to repeat the name of objectA anymore.

Example:

touch file.js && openEditor "$1" 

or something like that.

4
  • There's plenty of ways to do that, but depends on your needs. What's objectA ? a file ? What actions we're talking about ? Commented Jun 20, 2018 at 18:50
  • thanks, usually a file effectively, can also be a folder
    – HoCo_
    Commented Jun 20, 2018 at 19:28
  • 3
    Here, & will send the "touch" command into the background. If you intended "open the editor if touch succeeded", then you want && instead Commented Jun 20, 2018 at 19:51
  • Why are you bumping this post? You have already got plenty of answers and even accepted one.
    – muru
    Commented Jul 2, 2018 at 0:20

5 Answers 5

31

With bash History Expansion, you can refer to the nth word of the current command line using !#:n e.g.

$ touch foobar && ls -l !#:1
touch foobar && ls -l foobar
-rw-rw---- 1 steeldriver steeldriver 0 Jun 20 14:54 foobar

$ touch foo bar && ls -l !#:2
touch foo bar && ls -l bar
-rw-rw-r-- 1 steeldriver steeldriver 12 Jun 20 14:58 bar
8
  • May I ask, which bash version you're using ? Commented Jun 20, 2018 at 19:08
  • @SergiyKolodyazhnyy this is GNU bash, version 4.3.48(1)-release Commented Jun 20, 2018 at 19:09
  • 6
    you can also designate a range of words: touch foo bar && ls -l !#:1-2 Commented Jun 20, 2018 at 19:53
  • 1
    @HoCo_ the ! character introduces a history expansion expression when you're in an interactive shell - it's meant for on-the-fly command line manipulation as you're typing, and is disabled inside non-interactive scripts Commented Sep 30, 2018 at 12:44
  • 2
    @HoCo_ in a script you should know what the argument is ahead of time - so for example you could just do arg1=file.js; touch "$arg1" && openEditor "$arg1" Commented Sep 30, 2018 at 14:24
13

There is a handy shortcut for a common use case. In your example you are doing:

$ touch file.js
$ openEditor <alt>+<.>

In the second command, the trick is to write openEditor (with a space after it) followed by Alt+.. This will insert the last argument of the last command, which is file.js. (If it doesn't work with Alt for some reason, Esc should work as well.)

Since often the "object" is indeed the last argument of the previous command, this can be used frequently. It is easy to remember and will quickly integrate into your set of intuitively used shell shortcuts.

There is a whole bunch of things you can do with this, here's an in-depth article about the possibilities: https://stackoverflow.com/questions/4009412/how-to-use-arguments-from-previous-command.

As a bonus this will work not only in bash but in all programs that use libreadline for processing command line input.

4
  • 2
    You can combine this with a digit argument, e.g. to get the second argument hold Alt and press 2. (or Esc, 2, Esc, .), to get the second to last argument press Alt+-, type 2 and press Alt+. (or Esc, -2, Esc, .).
    – dessert
    Commented Jun 21, 2018 at 13:04
  • @dessert What if I run want more than one of the arguments? E.g. I run echo 1 2 3 4 then I want 2 and 3 for the next command
    – wjandrea
    Commented Jun 21, 2018 at 21:01
  • @dessert Found it myself! You just need to type something else between them, like a space. To concatenate them, the easiest way is to type a space then backspace.
    – wjandrea
    Commented Jun 21, 2018 at 21:08
  • 1
    @wjandrea hold Alt and type 2., press spacebar, hold Alt and type 3. – if you want a range, use history expansion: !!:2-3.
    – dessert
    Commented Jun 21, 2018 at 21:09
9

As far as default interactive shell bash and scripting shell dash goes, you can use $_ to recall last argument of the last command.

$ echo "Hello World"; echo same "$_"
Hello World
same Hello World

csh and tcsh have history references, specifically for the last word of the command, you can use !$, and for individual arguments - !:<index>:

~% echo foo bar
foo bar
~% echo !$
echo bar
bar

% echo bar baz
bar baz
% echo !:1
echo bar
bar

In general, it's just better to assign whatever is objectA to a variable, and use it in multiple commands, loops, etc. Alternatively, a function could be a choice:

$ foo(){ echo "$@"; stat --print="%F\n" "$@";}
$ foo testdir
testdir
directory
3
  • 1
    I'd just like to point out that $_ works slightly differently than !#:n as the latter is expanded before saving the command in history. So going back in the history would still show the command with $_ while !#:n would have been replaced with its actual value. Both have their pros and cons.
    – Dan
    Commented Jun 20, 2018 at 20:37
  • 3
    Also, as usual, $_ should be quoted, as otherwise it gets split and globbed like everything else. (We just don't see it here since echo joins its arguments with a single space.) But e.g. touch "hello there"; ls -l $_ won't work.
    – ilkkachu
    Commented Jun 21, 2018 at 13:51
  • 1
    @Dan I think the biggest pro to $_ is that it's portable. After all, !#:n is bash-specific. Commented Jun 26, 2018 at 3:43
5

I would recommend against the history approach given in steeldriver's answer. This relies on global state, which is always brittle.

Better is to upfront loop over all needed commands, using a proper variabe:

$ for c in touch gedit; do $c foo.txt; done

What's a bit of a problem in general is that Bash doesn't abort when there's a failure, i.e. this actually behaves like touch foo.txt; gedit foo.txt instead of chaining with &&. So, to be safe you can add a break:

$ for c in touch gedit; do $c foo.txt || break; done
1

When cooking up a one-liner, I sometimes assign my repeated thing to a shell variable which I use multiple times in the command. I can recall and edit it to use a different arg with up-arrow, control+a, control+right-arrow to get the cursor close to the t=.

t=testloop; asm-link -d "$t.asm" && perf stat -r2 ./"$t"

Note that this makes it easy to tack on an extension, or a variation on the name.

Also note that I need a ; after the variable assignment, because var=value cmd just sets that as an environment variable for that command, and doesn't affect the shell context.

You must log in to answer this question.

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