20

In Linux I sometimes rename files like this:

mv dir1/dir2/dir3/file.txt  dir1/dir2/dir3/file.txt.old

Note that I want to just rename the file, not move it to another directory.

Is there a command that would allow me to do a shorthand version of that command? I am thinking something like:

mv dir1/dir2/dir3/file.txt  file.txt.old

or maybe even something like (to just append to the name):

mv dir1/dir2/dir3/file.txt  {}.old

My goal is not to have to specify the complete path again.

I know those "examples" I wrote don't work, but it is just an idea of what I want to accomplish.

I don't want to have to cd in to the directory.

pipe
  • 107
  • 6
santiago arizti
  • 405
  • 5
  • 16
  • 8
    Why don't you change to the directory? It is fast and solves your problem. – Michael Hampton Jan 20 '18 at 17:59
  • I am dealing with many subdirectories from this same location, it is slower if I have to be coming back to this parent dir every time – santiago arizti Jan 20 '18 at 18:02
  • 3
    Nobody said you had to change to some other directory, only the one you want. – Michael Hampton Jan 20 '18 at 18:07
  • Note that `(cd dir1/dir2/dir3 && exec mv file.txt{,.old})` has effectively the same performance as the code without the subprocess, because the `exec` avoids `fork()`ing during the external command invocation, so you have exactly one fork either way. – Charles Duffy Jan 21 '18 at 21:10

7 Answers7

46

for a single file try

mv dir1/dir2/dir3/file.{txt,txt.old}

where the X{a,b} construct expand to Xa Xb, you can have a preview using

echo dir1/dir2/dir3/file.{txt,txt.old}

to see if it fit your need.

note:

  1. that for multiple files

    mv dir1/dir2/dir3/file{1,2,3}.{txt,txt.old}
    

is unlikely to expand to what you want. (this will expand to a mixed of file.txt file1.txt.old file2.txt ...)

  1. {txt,txt.old} can be shorterned to {,.old} as per comment

  2. if directory name are unambigous, wildcard can be used.

    mv *1/*2/d*/*.{,old}
    

for multiple file use rename

rename -n s/txt/old.txt/ dir1/dir2/dir3/file*.txt 

drop -n to have effective rename.

Archemar
  • 1,341
  • 11
  • 19
  • 1
    nice, I didn't know this way of expanding! thank you – santiago arizti Jan 20 '18 at 18:19
  • 14
    Would `mv dir1/dir2/dir3/file.txt{,.old}` not also work, and be even shorter? – ash Jan 20 '18 at 18:55
  • 3
    @Josh Yes it works perfectly and requires fewer keystrokes. I use this construct very regularly. – rlf Jan 20 '18 at 19:15
  • 2
    it is funny how you can use the Linux shell for years and still find new tricks all the time – santiago arizti Jan 21 '18 at 00:48
  • I guess all those "tricks" would be more obvious if I had had some formal training in the bash language, but I have been just learning enough to get along. – santiago arizti Jan 21 '18 at 00:51
  • @santiagoarizti thoses tricks came in long run, some through forum like this (along with U&L, SU, AskUbuntu ...), and sometime by reading carefully man pages (specially long one like `bash` or `awk`). To be clear I DID find tricks here and I DO find some trick reading man. – Archemar Jan 21 '18 at 17:14
  • Is there any "backward" command? For e.g. I want to rename file.txt.old to file.txt – ALex_hha Oct 19 '18 at 11:31
  • @ALex_hha just use the other way `mv file.txt{.old,}` – Archemar Oct 19 '18 at 13:47
6

The command rename renames all matched files. Its main purpose is to rename multiple files, but if the Perl expression matches only one file, then it will rename only this single file. For details and distro specific syntax please read the manpage: man rename.

Example usage: rename "/path/file.txt" to "/path/file.txt.old":

rename 's/file.txt/file.txt.old/g' /path/file.txt

Shorter notation (thanks to shalomb):

rename 's/$/.old/' /path/file.txt
rename '$_.=".old"' /path/file.txt

For a dry run use rename -n.

More examples and details can be found at https://thepcspy.com/read/bulk-rename-files-in-ubuntu/

Fabian
  • 335
  • 3
  • 16
  • cool, I knew about the rename command, just never knew all it could do, thank you! – santiago arizti Jan 20 '18 at 18:19
  • 6
    Unfortunately different Linux distributions have different `rename` commands with completely different behavior. – kasperd Jan 20 '18 at 19:26
  • 1
    For this particular task of renaming by appending `.old` to the filename, It's probably a bit more convenient to do a `rename 's/$/.old/' /path/to/file.txt` or `rename '$_.=".old"' /path/to/file.txt` – shalomb Jan 22 '18 at 12:33
  • @Fabian - I wasn't nitpicking, just aiming for the "shortest hand" for what seems to be a frequently used idiom the OP is looking for :) – shalomb Jan 22 '18 at 12:44
4

I like @archemar's solution of using {} expansion, because you can use it when you have already started your command line. Here are some other possibilities for completeness:

F="dir1/dir2/dir3/file.txt" ; mv "$F" "$F.old"

(quotes are only necessary if you have spaces or other word-breaking characters in the name)

mymv() {
    mv "$1/$2" "$1/$3"
}

mymv dir1/dir2/dir3 file.txt file.txt.old
mymv dir5/dir6/dir7 file.txt file.txt.old

Notes:

  • This is bash syntax, you might be using another shell
  • mymv can be any reasonable string, I used mymv for "my move".
  • The function definition can be put in your .bash_profile

Another one:

cd dir1/dir2/dir3 ; mv file.txt file.txt.old ; cd -

but

(cd dir1/dir2/dir3/ && exec mv file.txt file.txt.old)

(from @Adam and @CharlesDuffy) is arguably better in case the cd fails.

Law29
  • 3,507
  • 1
  • 15
  • 28
4

For a one-off case, if I want to not change my current directory I might just use a sub-shell:

(cd dir1/dir2/dir3/ && mv file.txt file.txt.old)

Because you cd inside the sub-shell, you haven't changed your directory in your actual shell

Adam
  • 266
  • 2
  • 4
  • 4
    Faster and safer to use `&& exec mv` -- that way you don't try to do the rename if the `cd` fails, and the `exec` consumes the subshell, thus avoiding a performance penalty. – Charles Duffy Jan 21 '18 at 21:11
  • Agree with the `&&` For a one-off case, as I said, I wouldn't worry about the cost of the sub-shell (keep it simple: being able to easily remember how to do it is more important IMO) – Adam Jan 24 '18 at 15:23
  • I'd argue that if you're going to remember only one way, remembering the efficient way helps make sure you don't go the inefficient route in cases where it *does* matter. – Charles Duffy Jan 24 '18 at 16:06
2

If you want to enter the subdirectory in order to shorten the mv command, but want to come back to the current directory afterwards, there are two ways to do that. One is to put the cd and mv in a subshell (as has already been suggested).

The other is to use pushd and popd to change directories:

pushd dir1/dir2/dir3/; mv file.txt file.txt.old; popd

Thepushd command is like cd, except it first pushes the name of the current directory onto a stack. popd removes the top name from the stack and changes to that directory.

AndyB
  • 131
  • 2
  • `pushd` and `popd` are extensions intended for interactive use -- POSIX explicitly lists them as "results are unspecified", and a shell built for running scripts (ie. on a minimal embedded environment) need not support them. – Charles Duffy Jan 24 '18 at 16:21
2

I would just do

cd dir1/dir2/dir3/
mv file.txt file.txt.old
cd ~- # go to the most recent directory
glglgl
  • 712
  • 1
  • 6
  • 22
0

The solution involving : mv something{.txt,.txt.old} are using a "bashism". If you want a more portable way, using common variable manipulations:

# rename all files "file*.txt" of directory /dir1/dir2/dir3 into file*.txt.old:
for f in dir1/dir2/dir3/file*.txt ; do
  mv "$f" "${f/.txt/.txt.old}"  # note there are no terminating "/"
done


# "${var/A/B}" is the content of var with the first occurence of "A" replaced by "B"
Olivier Dulac
  • 1,202
  • 7
  • 14