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
  • 8
  • I'd like to point out that if you do not mind keeping `file.tex` in the original location, [symlinks](https://en.wikipedia.org/wiki/Symbolic_link) are a very good alternative, since you only have to link once, and then it will always point to the latest version. – This company is turning evil. Aug 15 '16 at 14:12
  • 5
    Easier way: type `cd ` alt+`.` to substitute the last token of the previous command. Repeat to go further back in the history of final tokens. (I say token not arg, because `foo &` grabs `&` as the final token.) You can use a numeric argument (with escape-3 alt+. for example). – Peter Cordes Aug 15 '16 at 22:28
  • Further reading: [Is there any way to execute commands from history?](http://unix.stackexchange.com/a/275061/135943) and also [What is a fast command line way to switch between multiple directories for system administration?](http://unix.stackexchange.com/a/286628/135943) – Wildcard Aug 16 '16 at 07:39
  • see also [mkdir cd combo](http://superuser.com/questions/1073869/how-can-i-make-my-own-shell-commands-e-g-mkdir-cd-combo) – Christopher Bottoms Aug 16 '16 at 11:49

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
  • 54,820
  • 8
  • 124
  • 141
  • 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,654
  • 18
  • 27
  • 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 `` then `.` to get the same result as `+.`, 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
  • 7,676
  • 1
  • 25
  • 46
  • 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