24

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.

  • You should explain how you want to rename those files exactly. – Totor Jan 26 '15 at 15:06
  • 1
    Well i have files with an extension of *.gappedPeak and want to rename them to *.bed. they are all in the same directory it should be very simple but a full for loop was needed for me to succeed – Sander Van der Zeeuw Jan 26 '15 at 15:12
  • What you found on Google applies to MSDOS I think. – tiktak May 8 '16 at 18:46
  • @devnull 's alternate solution is really helpful – lucasvw Oct 4 '17 at 17:28
21

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.

28

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:

  1. '.' => search path starting at current directory marked by ' . '

  2. -name => set find match name (in this case all files that end with .gappedPeak)

  3. -exec => execute the following command on every match

  4. sh -c => 'exec' creates an independent shell environment for each match

  5. 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 is now just test#, with .bed, creating the new test#.bed filename.

  6. The underscore is a placeholder for $0

  7. The {} is replaced by each (*.gappedPeak) filename found by the find command, and becomes $1 to the sh command.

  8. \; 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
  • Could you go through the command describing what each part does? – user1717828 Jan 26 '15 at 17:16
  • I'm sorry about that, I updated my post. Hope that helps. – devnull Jan 26 '15 at 17:55
  • 1
    Awesome, thanks! With a bash guide and your explanation I was able to get everything. – user1717828 Jan 27 '15 at 22:51
  • No problem. Glad it helps. – devnull Jan 27 '15 at 22:57
  • Works like a charm. Here's another simplified example for 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
8

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.

  • 1
    cyberciti.biz/tips/… The first hit on google is saying that mv should work as well thats why i found it so strange – Sander Van der Zeeuw Jan 26 '15 at 15:12
  • 3
    @SanderVanderZeeuw I don't know what you read there. The examples use rename, not mv. – Hauke Laging Jan 26 '15 at 15:15
  • 1
    sorry i pasted the wrong HTML. This was the one udemy.com/blog/rename-a-file-in-linux – Sander Van der Zeeuw Jan 27 '15 at 14:00
  • 3
    @SanderVanderZeeuw Embarrassing. These people offer courses? Unfortunately I don't see a contact possibility there. You can easily check whether this works. But in 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
  • 1
    Or worse. If one have one file named e.g. foo.txt and one named baz.tsv the mv *.txt *.tsv will overwrite the existing .tsv file ... – Runium Jan 27 '15 at 17:38
4

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.

  • Thanks, this clarifies partially. Still i find it strange that i see so many manual pages where they claim mv should work on multiple. – Sander Van der Zeeuw Jan 26 '15 at 15:14
2

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

Installation

Debian/Ubuntu

If you need to install rename on Debian/Ubuntu you can do

sudo apt install rename
0

Another option to consider is using:

cp -p *.txt *.tsv
rm -f *.txt
  • The first line copies all *.txt files to *.tsv whilst preserving their attributes using the -p flag.
  • The second line removes all files with the pattern *.txt
  • This method requires you have enough disk space to temporarily hold both the source and target files
  • This method is slower than mv which simply changes the /directory/filename without actually moving the files unless they are on a different partition.

protected by Anthon May 8 '16 at 18:46

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.