5

Suppose I have a path like this under some directory (e.g. home): ~/mydir/subdir/file1,and I want to make a copy of file1:

We could do this from the home directory:

cp ~/mydir/subdir/file1 ~/mydir/subdir/file2

Is there a shortcut that means "in the same path as the source" so I don't have to repeat the mydir/subdir portion again, just like . means the current directory? Or another way to achieve the same result?

I know I can cd ~/mydir/subdir first, but I'm looking for a one line solution that would allow me to stay in the same directory. One option would be to use a variable, but I'm hoping there's a more elegant way when typing lots of commands.

Had a look at man cp and searched online but couldn't find such an option. If the answer is a definite "no" that's fine too, just thought I'd check if there's a time saving trick others use. Thanks!

Nagev
  • 439

4 Answers4

12

using bash (1), and assuming path without space or new line (2), I would use

cp ~/mydir/subdir1/{file1,file2}

this would be expanded by bash to (along with ~ expansion)

cp ~/mydir/subdir1/file1 ~/mydir/subdir1/file2

as always you can test using echo

echo cp ~/mydir/subdir1/{file1,file2}

As per comment :

(1) this may work on other shell, I didn't have time to test them all.

(2) for "funny" names, either use tab completion (so bash will put proper escape), or use quotes (but not arround braces) :

mv ./"some dir"/{"some name","other id"}
Archemar
  • 31,554
  • 2
    Note that that syntax is from csh in the late 70s, it's not specific to bash and is supported by most shells nowadays. – Stéphane Chazelas Aug 07 '22 at 16:23
  • There is no issue with spaces or newlines as word splitting happens after brace expansion. – Kusalananda Aug 07 '22 at 17:07
  • 3
    just need to quote the insides of the brace expansion (but not the braces). something like mv ./"some dir"/{"some name","other id"} should work – ilkkachu Aug 07 '22 at 17:59
  • “as always you can test using echo”, thanks, I didn't that existed and it's awesome! – A.L Aug 08 '22 at 09:52
7

With a subshell ( ... ) one can chdir into the desired directory and if that chdir did not fail perform the copy. The chdir only influences the working directory of the subshell, not of the parent shell process.

( cd ~/mydir/subdir && cp file1 file2 )

This could be abstracted into a shell function without too much difficulty, and tab completion would not be very hard to make relative to the offset working directory (at least for ZSH).

thrig
  • 34,938
5

Using find with -execdir (where supported, initially from BSD, but also supported by GNU find)

but the specified command is run from the subdirectory containing the matched file

For example

find ~/mydir/subdir/file1 -prune -execdir cp "{}" file2 \;

(-prune to make sure that if ever file1 is of type directory, find doesn't end up copying every file within to file2; cp would complain in any case if asked to copy a directory without -r / -a).

Hans Chen
  • 298
3

Interactively, there's an icp command like imv, that gives you a readline editing environment with the filename. It's in the "renameutils" package. See the man page

...$ icp   ~/foo/bar/baz
> /home/user/foo/bar/baz       # edit this and hit return.  it runs cp for you

Tab completion is available while editing, at least if the path doesn't contain spaces. It seems to fail with those, unfortunately.

They prompt before overwriting the destination file if it exists.

icp and imv have some default options they pass to cp or mv respectively, including cp's --no-preserve (of attributes like timestamps).


Or, line-editing features make it very easy and fast to duplicate the path without typing it again.

  • Start by typing cp ~/mydir/subdir/ (tab completion is available)

  • If the path contains no spaces, a single ctrl-w will "kill" it, storing it in the kill ring. If there are spaces, it'll take more than 1, and if you overshoot and kill the cp as well, it'll be part of the kill-ring entry even if you use ctrl-/ to undo the overshoot.

    Or ctrl-a, ctrl-right-arrow to place the cursor after the cp, then ctrl-k to kill-to-end-of-line.

  • Yank back what you killed with ctrl-y, so your command line is back to step 1, but with the directory ready to paste

  • type the source filename (tab completion is available)

  • Hit space, then yank another copy of the path with ctrl-y. Now your command line looks like
    cp ~/mydir/subdir/file1 ~/mydir/subdir/

  • Type the final filename (tab completion is available)

The same building blocks can be used in other orders. For example if you started typing or copy/pasting the source path including filename, and only after that you realize you want the copy to be in that directory, ctrl-left-arrow to move the cursor backward-word before killing/yanking the directory part, then ctrl-e for end of line before yanking another copy of it.

Or if you want to give the copy a similar filename, kill / 2x yank the whole path including filename, then edit the 2nd copy (destination). Again, tab-completion is available at all stages.

If you accidentally kill something else so ctrl-y yanks the wrong thing, alt-y to cycle back through previous kill-ring entries.

These interactive line-editing tools are extremely useful all the time, not just for cp. Cursor-movement by "word", and kill forward/backward word, are highly useful for quickly editing and copying paths.

Peter Cordes
  • 6,466