Ubuntu – Moving files accidentally to an not existing directory erases files

command linefilesmv

I moved all files from one directory using mv and accidentally made a typo in the target location path.

The system returned a message that the directory does not exists, but my files from source directory got erased.

Is that a bug? Should moving files to a nonexistent location erase the files being moved? (This is on Ubuntu 18.04.2 LTS.)

The specifics were:

  1. Created test.txt file.
  2. Moved the file to /ben with sudo.
  3. The file disappeared. /ben does not exist.

The commands and output were:

ben.b@c-w46:~/Desktop/test-folder$ sudo mv test.txt /ben
ben.b@c-w46:~/Desktop/test-folder$ cd /ben
bash: cd: /ben: Not a directory

Best Answer

In the command you actually ran, you didn't lose anything! It succeeded at renaming test.txt to /ben. Assuming test.txt was a regular file, so is the new /ben (they're the same file, after all).

The reason you see bash: cd: /ben: Not a directory is what it says on the tin: /ben is not a directory. You can still access the file.

If you want to avoid that sort of mistake and force mv to fail if the destination isn't a directory, write a trailing / on it or use -t dir. For example, any of these would've prevented the (very minor!) problem you experienced (with sudo as necessary):

mv test.txt /ben/  # no action, unless `/ben` is an existing directory
mv -t /ben test.txt  # same deal, -t doesn't accept regular-file operands
mv -t /ben/ test.txt  # you can even do both if you want

Information about the general situation described in your question--about losing files by attempting to move them--and what can and can't go wrong follows.

As Rinzwind says, moving files to a nonexistent destination directory shouldn't cause data loss when you attempt it using a single mv command. But it could happen if you ran mv more than once, such as in a shell loop.

For example, suppose I have:

ek@Apok:~/tmp$ ls -F
dest/       file02.txt  file04.txt  file06.txt  file08.txt  file10.txt
file01.txt  file03.txt  file05.txt  file07.txt  file09.txt

To move all those files into dest, I should pass all their names to mv, in a command like mv file*.txt dest/ or mv file*.txt dest. In either case--that is, whether or not I write the target directory name with a trailing slash--this does the right thing. And in either case, if I misspell the target directory name (say, by writing dst instead) I get an error mv: target 'dst' is not a directory and no data are lost.

However, suppose I were to misspell dst, omit the trailing /, and run multiple mv commands. That would be bad, because when the destination of mv is a regular file, mv replaces it!

ek@Apok:~/tmp$ mv file01.txt dst  # bad if dst exists but isn't a directory
ek@Apok:~/tmp$ mv file02.txt dst  # bad, I just lost the old file01.txt!

This is why many people prefer always to write destination directories with a trailing / in mv:

ek@Apok:~/tmp$ mv file03.txt dst/
mv: failed to access 'dst/': Not a directory

You can use mv -i to ask you before overwriting, or mv -n to silently not overwrite. Otherwise, mv only ask you before overwriting if the destination is a read-only file. One reason to consider this is that it covers other cases, like mv file01.txt dest/ where you didn't realize dest/file01.txt existed and didn't want to overwrite it.

You can also use -t dest instead of writing dest at the end of the command, e.g., mv -t dest file*.txt. This refuses to operate if dest is a regular file, regardless of whether or not you write a trailing /.

Using an automated mechanism to run multiple such commands can gravely compound the problem. For example, as written the command for f in file*.txt; do mv "$f" dest/; done is needlessly complicated but safe, because if I accidentally specified the file dst instead of the directory dest (but kept the slash!), it would give me one mv: failed to access 'dst/': Not a directory error per file. However, if I omitted the trailing /, then it would rename each file to dst, replacing the previous dst, and only the last file would remain.

Similar bad outcomes can be achieved with find, including in situations where it may be reasonable to use find (but differently, and still with extra care). For example, suppose I wanted to move all files matching the glob file*.txt in an entire directory tree (except in dest itself) into the directory dest. I might first think to use this:

find . -path ./dest -prune -o -name 'file*.txt' -exec mv {} dest/ \;  # bad, don't use

Because I included a trailing /, writing dest/ instead of dest, this wouldn't overwrite a file called dst even if I wrote dst instead of dest. But it has the related problem that it will overwrite files that it has already copied, if files in different parts of the directory tree have the same name. For example, if there's an a/file01.txt and a b/file01.txt, one will overwrite the other. To avoid that, too, it's better to use something like this:

find -path ./dest -prune -o -name 'file*.txt' -exec mv -it dest/ {} \;  # okay

The other benefit of -t dir is that, because it lets you specify the destination directory before the items being moved, it's compatible with the + form of -exec, where multiple items are passed to a command, thereby running fewer commands (often just one):

find -path ./dest -prune -o -name 'file*.txt' -exec mv -it dest/ {} +  # good

In both cases (they're the same except for \; vs. +) I've also passed the -i option to prompt before each operation that would overwrite a file. If you just want to silently skip those, write n instead of i. If you want to test your find commands first, you can write echo after -exec but before the rest of the command to print what would be run. For example:

ek@Apok:~/tmp$ find -path ./dest -prune -o -name 'file*.txt' -exec echo mv -it dest/ {} +
mv -it dest/ ./file02.txt ./file06.txt ./file10.txt ./file09.txt ./file01.txt ./file04.txt ./file05.txt ./file07.txt ./file03.txt ./file08.txt

(Of course, that's in the original directory I showed, where all the files to moved are in the same location, and thus where find is overkill and the most complicated reasonable command to use is mv -it dest/ file*.txt.)

Related Question