17

I've just lost a small part of my audio collection, by a stupid mistake I made. :-(
GLADLY I had a fairly recent backup, but it was still irritating. Apart from yours truly, the other culprit doing the mischief was mv, which will show as follows:

The audio files had a certain scheme:

ARTIST - Some Title YY.mp3

where YY is the 2-digit year specification.

mkdir 90<invisible control character>

(Up to this moment, I did not know that I had actually typed one third excess character which was invisible ...!)
Instead of having all in one directory, I wanted to have all 1990s music in one directory. So I typed:

find . -name '* 9?.mp3' -exec mv {} 90 \;

Not so hard to get the idea what happened eh? :->
The (disastrous) result was a virgin empty directory called '90 something' (with something being the "invisible" control character) and one single file called '90', overwritten n times.

ALL FILES WERE GONE. :-(( (obviously)

Wish mv would've checked in time whether the signature of the destination "file" (remember on *NIX: Everything Is A File) starts with a d------ (e. g. drwxr-xr-x). And, of course, whether the destination exists at all. There is a variant of the aforementioned scenario, when you simply forgot to mkdir the directory first. (but of course, you assumed that it's there...)

Even our pet-hate OS starting with the capital W DOES DO THIS. You get even prompted to specify the type of destination (file? directory?) if you ask for it.

Hence, I'm wondering if we *NIXers still have to write ourselves a "mv scriptlet" just to avoid these kinds of most unwanted surprises.

6
  • 2
    Not all files were gone. At least one .mp3 should be there with the name 90, it could have been one for which you did not have a backup.
    – Anthon
    Nov 21, 2014 at 8:38
  • 2
    Heh, you've got a cynical sense of humor, you nut you! :-P Well, that was the file called the "one single file" in bold print in my OP. :) Nov 21, 2014 at 8:45
  • 2
    mv isn't the problem here, technically, it doesn't know that you are moving a series of files. You are running mv one time for each file. That's how find -exec ; works. If you had used find -exec + (as in some of the comments) mv would have screamed as soon as it got more than one argument. Nov 23, 2014 at 12:49
  • Though running mv for each single file might seem a bit less thought-out at first, it will (as I had said previously) be the only sane solution once source files are scattered among various subdirectories. That in my test-case, source files were all in one directory does not mean that it's my actual test case. It's in fact just a simplification, because I may easily elaborate on that on my own later. Plus, it makes questions less time-consuming to read due to their reduced length. :) Nov 23, 2014 at 13:03
  • Why would you expect mv to require that the destination exists? mv oldfile newfile is the way to rename a file, and it's silly to expect newfile to exist already and be a directory.
    – Barmar
    Nov 26, 2014 at 19:29

6 Answers 6

37

You can append a / to the destination if you want to move files to a directory. In case the directory does not exist you'll receive an error:

mv somefile somedir/
mv: cannot move ‘somefile’ to ‘somedir/’: Not a directory

In case the directory exists, it moves the file into that directory.

3
  • 4
    Thank you so much! That should be my future life-saver then. I owe you one. (Just as a note, a friend of mine had made the very same mistake some years ago, so I feel I'm not alone.) Nov 21, 2014 at 8:43
  • 1
    Also, when using certain Shells, you have the Tab option to auto-complete filenames for you. If I can't quite remember the directory name, and don't want a mess like you recently had, just punch in a character or two of the directory name and HIT TAB. Then you can be Sure, that it exists, because auto-complete put it there.... Nov 23, 2014 at 13:47
  • @AndyzSmith Well, that's the very thing. You may call it a habit of mine to only ever use TAB for complicated directories or paths, but not for 2-letter-type ones.:) But come to think of it...perhaps I should really consider the latter case as well from now on. Dec 12, 2014 at 5:20
20

The GNU coreutils mv already has an option specifying that you want to move to a directory: -t / --target-directory. If the argument to that option doesn't exists, mv will complain instead of moving all of your files to the same filename.

I would have written your mover as follows:

find . -name '* 9?.mp3' -exec mv -t 90 {} +

Note the use of + instead of \;, globbing as many filenames together as possible, resulting in faster execution.

6
  • Thanks. (Hoping it's not one of those GNU-isms again, though.) Nov 21, 2014 at 8:41
  • 2
    @syntaxerror. It is a GNUism. POSIXly: find . -name '* 9?.mp3' -exec sh -c 'exec mv "$@" 90/' sh {} + Nov 21, 2014 at 15:39
  • Thank you very much for this most SNEAKY one-liner! Just think it were a +5 I gave you. :) Will make lots of trial-and-error attempts unnecessary.--- And you know too well why I made that remark. Just need to be on a machine that is straightly POSIX (doesn't happen too rarely), and I'm going to have the next problem right there. :) Nov 21, 2014 at 15:50
  • 1
    @syntaxerror If this answer solves your issue please take a minute and click the check mark under the vote count to the left, this will signify to everyone that your issue's been resolved and is the way that thanks are expressed in the site. I saw that only a few of your other answered questions have accepted answers, you might want to reveiw those too.
    – Anthon
    Nov 21, 2014 at 16:49
  • No, it's simply purpose. I'd frequently wait several weeks or sometimes 2 months until I accept an answer. The reason is that some very knowledgeable people have a VERY loaded schedule and might only find time to give their (usually best) answer after a couple weeks. So I always find it just respectful to wait for them to stop by. Well, and if they really don't, I won't hesitate to hit the checkmark, for sure. Besides, I don't see why some people are always in a hurry so much on SE + its flavors. Easy does it, fellas. Don't jump the gun. :) This ain't your boss pressing on you. Nov 21, 2014 at 17:38
10

Additionally, if you generally plan to avoid accidental overwrites in the future, there is the -i option for mv. I personally cannot think of any drawbacks if you

alias mv='mv -i'

If you then need to overwrite something, simply pass the -f option.

Aliases only take effect if you type the command directly into an interactive shell, not for cases like invocation by find. You could have run

find . -name '* 9?.mp3' -exec mv -i {} 90 \;

and then you would have been prompted if mv had tried to overwrite an existing file.

18
  • 4
    Or - as you probably did not know - type ´ \mv ` instead of mv. This less known "trick" will cause the command with the backslash prepended to it to ignore any alias definitions. Nov 21, 2014 at 15:21
  • Of course, if you're really talking about find ... -exec mv ..., then you'll need to create ~/bin/mv (or some other appropriate directory) and have it do /bin/mv -i "$@" -- because find ... -exec doesn't look at aliases. Nov 21, 2014 at 20:41
  • In this case, I'd prefer env mv. Less typing. :) As my local keyboard layout requires the SHIFT key to be pressed for a forward slash, I would always favor "slash-less" versions (if applicable). Nov 22, 2014 at 6:33
  • 1
    @syntaxerror: BTW, when you respond to comment (in a new comment), it’s conventional to mention the author’s name, preceded by “@”, as in “@G-Man”. That way I get notified. (I was able to respond to your last comment in a semi-timely manner because I got notified by Jenny D’s comment.) You can abbreviate, or use an entire name (without spaces), e.g., “@StéphaneChazelas”. The author of a post is automatically notified of comments to that post. See the Replying in comments paragraphs of this help page. Nov 22, 2014 at 21:05
  • 1
    I'm sorry, I was Internet-less for a day. @G-Man I agree that having a personal version of mv in a location at the beginning of $PATH would be a cleaner solution. On the other hand I primarly happen to mv carelessly in the interactive shell (because it happens fast). The moment I compose something more complex, like find or a for loop, or even a shell script, I tend to perform some dry runs (using echo) to ensure I don't break something. In those cases I don't need a hand-holding mv, because I'm already putting some thoughts into it.
    – ayekat
    Nov 23, 2014 at 0:49
7

In addition to the excellent answers above, I'd like to clarify why you didn't get the question about whether to move the files or not.

If you move one file to a new name, and that name is not a directory, mv will rename your file to the new name.

The issue here is that you were using find to execute mv once per file, not once for all the files.

If, instead, you'd done mv *90.mp3 90, then mv would have failed with the error message that "the target file is not a directory".

Another piece of advice is to use tab completion when typing the target path. It will show you whether the target is a directory by adding / to the target name. You can also use mv -i to be asked whether you want to overwrite an existing file.

9
  • If, instead, you'd done mv *90.mp3 90, then mv would have failed with the error message that "the target file is not a directory". Hah, yeah, why so complicated eh? I use your line and I'll be happy. Only in this trivial case, though. :) Because this is the normal way I ask my questions: I'd have them narrowed down for simplicity's sake. No one has objected to find so far considering that the 90's files might as well be scattered around in various subdirectories which I want to "catch" as well. If and only if they're always in one source directory, your mv line is applicable. Nov 21, 2014 at 9:30
  • @syntaxerror Very true - I meant this as an example of how mv behaves, not as a criticism of your choice of tools.
    – Jenny D
    Nov 21, 2014 at 9:35
  • 1
    You could probably build something combining find and mv, e.g. find /music -type d -exec mv {}/*90.mp3 targetdir\; - but now I feel a bit like I'm overcomplicating it, and simply using -i or -t is more efficient
    – Jenny D
    Nov 21, 2014 at 9:41
  • 1
    @Jenny: If your find . -type d -exec mv {}/*9?.mp3 target \; example worked, there would still be the risk that the mv command would look like mv file target for each directory that contained only one *9?.mp3 file; so all such files (except for the last one) would be lost. Nov 21, 2014 at 20:35
  • 4
    @syntaxerror: I applaud your effort to expose the essential part of your problem, rather than the entire 42,000 line script in which it occurs. But, even if you did want to do something to all *.mp3 files in a directory tree, you could shopt -s globstar and then run your command on **/*.mp3 -- the ** will act like a find. Nov 21, 2014 at 20:38
1

As an alternate general-purpose strategy I'd like to suggest turning this kind of operation into a temporary script. I prefer to look at the results of find and turn them into a mv command by hand, ensuring that I understand what I'm doing before I execute. eg.

find . -name '* 9?.mp3' > tmp
vim tmp

Now I can look through a list of filenames and rewrite the file contents as a shell command.

  • Put the file contents on one line: ggVGJ
  • Prepend: Imv [esc]
  • Append: Asomedir/ [esc]
  • Save the file. Read it again. Take a breath.
  • Execute source tmp on the command line.

It is a conservative strategy, but I've been bitten too often by mistyped -exec or sed commands, or misunderstood shell expansions, and I prefer taking a slow consistent approach.

In other words: I am too cowardly to use -exec.

3
  • 1
    You left out the :%s/.*/"&"/ step -- because, in this case, you know that every filename contains at least one space. Nov 21, 2014 at 20:44
  • 1
    This will bite you if the file names contain spaces or other special characters. You need to quote them properly. Reviewing a list of commands isn't particularly likely to catch errors. There are far better ways to review commands before running them, such as runing echo mv instead of mv, and then removing the echo if you're happy. Nov 21, 2014 at 23:16
  • Spotting a mistake like filenames-with-spaces is precisely what this technique will help with :-)
    – Tom Rees
    Nov 21, 2014 at 23:40
0

Another option:

-n, --no-clobber do not overwrite an existing file

it's the same as -i, but it wont ask, it'll fail.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .