Rename multiple files with mv to change the extension
I know this doesn't answer your question, but in case you were looking for another way to rename the files compared to your work-around loop, why not use find
? I have used this command many times to replace file extensions in large directories with hundreds of thousands of files in it. This should work on any POSIX-compliant system:
find . -name "*.gappedPeak" -exec sh -c 'mv "$1" "${1%.gappedPeak}.bed"' _ {} \;
Command Breakdown:
'
.
' => search path starting at current directory marked by ' . '
-name
=> set find match name (in this case all files that end with.gappedPeak
)
-exec
=> execute the following command on every match
sh -c
=> 'exec' creates an independent shell environment for each match
mv "$1" "${1%.gappedPeak}.bed"
=>mv
first variable (denoted by $1), which is the current file name, to new name. Here I do a substring match and delete; so take first var again, $1 and use%
to delete.gappedPeak
from the string. The.bed
at the end just concatenates the remaining variable, which in the example below would now betestNumber
, with.bed
, creating the newtestNumber.bed
filename.The underscore is a placeholder for $0
The
{}
is replaced by each (*.gappedPeak
) filename found by thefind
command, and becomes $1 to thesh
command.
\;
marks the end of the-exec
command. You can also use';'
or";"
.
Example:
[user@before]# ls -lh
total 0
-rw-r--r--. 1 root root 0 Jan 26 11:40 test1.gappedPeak
-rw-r--r--. 1 root root 0 Jan 26 11:40 test2.gappedPeak
-rw-r--r--. 1 root root 0 Jan 26 11:40 test3.gappedPeak
-rw-r--r--. 1 root root 0 Jan 26 11:40 test4.gappedPeak
-rw-r--r--. 1 root root 0 Jan 26 11:40 test5.gappedPeak
[user@after]# ls -lh
total 0
-rw-r--r--. 1 root root 0 Jan 26 11:40 test1.bed
-rw-r--r--. 1 root root 0 Jan 26 11:40 test2.bed
-rw-r--r--. 1 root root 0 Jan 26 11:40 test3.bed
-rw-r--r--. 1 root root 0 Jan 26 11:40 test4.bed
-rw-r--r--. 1 root root 0 Jan 26 11:40 test5.bed
When you issue the command:
mv *.txt *.tsv
the shell, lets assume bash, expands the wildcards if there are any matching files (including directories). The list of files are passed to the program, here mv
. If no matches are found the unexpanded version is passed.
Again: the shell expands the patterns, not the program.
Loads of examples is perhaps best way, so here we go:
Example 1:
$ ls
file1.txt file2.txt
$ mv *.txt *.tsv
Now what happens on the mv
line is that the shell expands *.txt
to the matching files. As there are no *.tsv
files that is not changed.
The mv
command is called with two special arguments:
argc
: Number of arguments, including the program.argv
: An array of arguments, including the program as first entry.
In the above example that would be:
argc = 4
argv[0] = mv
argv[1] = file1.txt
argv[2] = file2.txt
argv[3] = *.tsv
The mv
program check to see if last argument, *.tsv
, is a directory. As it is not, the program can not continue as it is not designed to concatenate files. (Typically move all the files into one.) Nor create directories on a whim.
As a result it aborts and reports the error:
mv: target ‘*.tsv’ is not a directory
Example 2:
Now if you instead say:
$ mv *1.txt *.tsv
The mv
command is executed with:
argc = 3
argv[0] = mv
argv[1] = file1.txt
argv[2] = *.tsv
Now, again, mv
check to see if *.tsv
exists. As it does not the file file1.txt
is moved to *.tsv
. That is: the file is renamed to *.tsv
with the asterisk and all.
$ mv *1.txt *.tsv
‘file1.txt’ -> ‘*.tsv’
$ ls
file2.txt *.tsv
Example 3:
If you instead said:
$ mkdir *.tsv
$ mv *.txt *.tsv
The mv
command is executed with:
argc = 3
argv[0] = mv
argv[1] = file1.txt
argv[1] = file2.txt
argv[2] = *.tsv
As *.tsv
now is a directory, the files ends up being moved there.
Now: using commands like some_command *.tsv
when the intention is to actually keep the wildcard one should always quote it. By quoting you prevent the wildcards from being expanded if there should be any matches. E.g. say mkdir "*.tsv"
.
Example 4:
The expansion can further be viewed if you do for example:
$ ls
file1.txt file2.txt
$ mkdir *.txt
mkdir: cannot create directory ‘file1.txt’: File exists
mkdir: cannot create directory ‘file2.txt’: File exists
Example 5:
Now: the mv
command can and do work on multiple files. But if there is more then two the last has to be a target directory. (Optionally you can use the -t TARGET_DIR
option, at least for GNU mv.)
So this is OK:
$ ls -F
b1.tsv b2.tsv f1.txt f2.txt f3.txt foo/
$ mv *.txt *.tsv foo
Here mv
would be called with:
argc = 7
argv[0] = mv
argv[1] = b1.tsv
argv[2] = b2.tsv
argv[3] = f1.txt
argv[4] = f2.txt
argv[5] = f3.txt
argv[6] = foo
and all the files end up in the directory foo
.
As for your links. You have provided one (in a comment), where mv
is not mentioned at all, but rename
. If you have more links you could share. As well as for man pages where you claim this is expressed.
mv *.txt *.tsv
doesn't work; mv
can rename only one file at a time. You have either misunderstood the explanations or they are wrong.
mmv
and rename
can rename several files at once. But there are two versions of rename
around which are called differently. There should be plenty of questions about that here.