15

I would like to construct a short function to do the following. Let's say that I move file 'file.tex' to my documents directory:

mv file.tex ~/Documents

Then, I'd like to cd to that directory:

cd ~/Documents

I'd like to generalize this to any directory, so that I can do this:

mv file.tex ~/Documents
follow

and have the follow command read the destination from the previous command, then execute accordingly. For a simple directory, this doesn't save much time, but when working with nested directories, it would be tremendous to be able to just use

mv file.tex ~/Documents/folder1/subfolder1
follow

I thought it would be relatively simple, and that I could do something like this:

follow()
{
    place=`history 2 | sed -n '1p;1q' | rev | cut -d ' ' -f1 | rev`
    cd $place
}

but this doesn't seem to work. If I echo $place, I do get the desired string (I'm testing it with ~/Documents), but the last command returns

No such file or directory

The directory certainly exists. I'm at a loss. Could you help me out?

Fire
  • 361

3 Answers3

18

Instead of defining a function, you can use the variable $_, which is expanded to the last argument of the previous command by bash. So use:

cd "$_"

after mv command.

You can use history expansion too:

cd !:$

If you must use a function:

follow () { cd "$_" ;}

$ follow () { cd "$_" ;}
$ mv foo.sh 'foo bar'
$ follow 
foo bar$ 

N.B: This answer is targeted to the exact command line arguments format you have used as we are dealing with positional parameters. For other formats e.g. mv -t foo bar.txt, you need to incorporate specific checkings beforehand, a wrapper would be appropriate then.

heemayl
  • 56,300
  • Your history expansion (cd !:$) works perfectly. Thank you.

    However, the other (cd "$_") does not:

    mv file.tex ~/Downloads/ cd "$_"

    bash: cd: __bp_preexec_invoke_exec: No such file or directory

    I will very gladly accept your answer as entirely correct, and thank you.

    – Fire Aug 15 '16 at 13:40
  • 1
    I have always typed (somewhat wastefully) either cd !$ or cd $(dirname !$). Didn't know about the $_ variable! – Kalvin Lee Aug 15 '16 at 14:13
  • Now what happens if I do mv -t ~/Documents file.tex instead of mv file.tex ~/Documents? Bottom line, I'm not sure this is solvable in the general case... a wrapper function around or that reimplements mv might be better... – user Aug 15 '16 at 15:11
  • @MichaelKjörling Won't work simply..this example is just to cover the case OP has, not the edge (or all) cases.. – heemayl Aug 15 '16 at 15:12
  • You don't need the colon; !$ is equivalent to !:$ and faster to type. – Wildcard Aug 16 '16 at 07:38
  • Note the difference between !$ and "$_" when run after mv file "dir$((++n))" for instance. – Stéphane Chazelas Aug 16 '16 at 12:34
14

With standard bash keybindings, the combination Alt. will copy the last argument of the previous command line into the current one. So, typing

$ mv foo ~/some/long/path/
$ cd <Alt><.>

would yield

$ mv foo ~/some/long/path/
$ cd ~/some/long/path/

and would be even less typing than the word follow.

For added convenience, repeating the Alt. combination will browse through the last arguments of all previous command lines.

Addendum: The bash command name corresponding to this key combination is yank-last-arg or insert-last-argument. It can be found in the bash manpage under "Commands for Manipulating the History" or in the more exhaustive Bash Reference Manual.)

Dubu
  • 3,723
  • 1
    That's clever. Thanks! I had no idea this existed. – Fire Aug 15 '16 at 14:57
  • @Fire I added references for those commands, so you can find a lot more interesting key bindings (and immediately forget them again, like I always do). – Dubu Aug 15 '16 at 15:26
  • 1
    You can also use <esc> then . to get the same result as <alt>+., this useful when you have remapped capslock to escape :) – gnur Aug 16 '16 at 10:42
  • 1
    @gnur vi(m) user, I suppose. ;-) – Dubu Aug 16 '16 at 18:32
6

You're almost certainly running into the problem that tilde expansion takes place before parameter expansion, which can be explained by a succinct example:

$ cd ~kaz
kaz $ var='~kaz'
kaz $ echo $var
~kaz
kaz $ cd $kaz
bash: cd: ~kaz: No such file or directory

This can be addressed with eval. Anyway, you're going to need eval, because you're pulling commands from the history and they can contain arbitrary expansions, like:

$ mv file.tex ~/Documents/$(compute_folder_name foo-param)/subfolder1
$ follow

(There are issues, such as that the re-expansion of these values might no longer match the original expansion which occurred. Suppose compute_folder_name is a function which increments some global variable.)

Kaz
  • 8,273
  • 4
    You don't need eval. (Ever.) But good spotting on the expansion sequence problems. If you mention the greater advisability of history expansion to using eval here, this will be the best answer in my opinion; none of the others actually explain why the Original Poster's solution failed to work. – Wildcard Aug 16 '16 at 07:37