I want to rename files to change their extension, effectively looking to accomplish
mv *.txt *.tsv
But when doing this I get :
*.tsv is not a directory
I find it somewhat strange that the first 10 google hits show mv
should work like this.
I want to rename files to change their extension, effectively looking to accomplish
mv *.txt *.tsv
But when doing this I get :
*.tsv is not a directory
I find it somewhat strange that the first 10 google hits show mv
should work like this.
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
file.abc
-> blub.xyz
in multiple sub dirs: find . -name "file.abc" -exec sh -c 'mv "$1" "$(dirname $1)/blub.xyz"' _ {} \;
– Mahn
Apr 12 '16 at 15:17
ENVIRONMENT
.
– AdminBee
Jan 04 '23 at 12:18
find . -name "*.js" -exec sh -c 'mv "$1" "${1%.js}.jsx"' _ {} \
– Nivethan
Jan 28 '23 at 00:53
This answer won't help you change the extension, but it will help you understand why your command is not doing what you expect.
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 is 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 to understand why this won't work. So here we go:
$ 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 checks to see if the last argument, *.tsv
, is a directory. As it is not, the program can not continue as it is not designed to concatenate files. (combine all the files into one.) Nor can it create directories on a whim.
As a result, it aborts and reports the error:
mv: target ‘*.tsv’ is not a directory
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
checks 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
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 end 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"
.
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
Now: the mv
command can and does work on multiple files. But if there are more than two the last one 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
. Do you have more links or man pages that you could share where your claim 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.
rename
, not mv
.
– Hauke Laging
Jan 26 '15 at 15:15
mv *.txt *.tsv
mv
does (usually) not see *.txt
or *.tsv
but the shell-expanded file names. The number of files these wildcards expand to would be "random". The only situation where this works is if there is a file with the name *.txt
which shall be renamed to (literally) *.tsv
(without quoting the bash
option nullglob
must not be set).
– Hauke Laging
Jan 27 '15 at 15:29
foo.txt
and one named baz.tsv
the mv *.txt *.tsv
will overwrite the existing .tsv
file ...
– Runium
Jan 27 '15 at 17:38
rename(1)
rename
is a perl script by Larry Wall the maker of perl. It takes a Perl regex and operates on the file name.
rename 's/\.txt$/.tsv/' *.txt
If you need to install rename
on Debian/Ubuntu you can do
sudo apt install rename
find . -type f -name '*.txt' | xargs rename 's/\.txt$/.tsv/'
– Evan Carroll
Feb 06 '22 at 18:44
For example, if you have asd.txt
and qwe.txt
files in the directory when you run the command mv *.txt *.tsv
, it tries to move these two files into a directory named *.tsv
. Because there is no such directory, it gives an error.
Apparently the easiest way under Linux OS is :
I guess sometimes DOS does win. :)
file1.txt
to file1.tsv
. (2) It’s implicit in the question (and in the other answers) that the objective is to find *all* the files that have the specified “old” extension *automatically* and then rename them all to have the same name but with the new extension, and do this all in one operation … (Cont’d)
– G-Man Says 'Reinstate Monica'
Feb 10 '20 at 06:03