Moving files accidentally to an not existing directory erases files?
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:
- Created
test.txt
file. - Moved the file to
/ben
withsudo
. - 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
Solution 1:
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
.)
Solution 2:
No, what you suggest should(!) not be possible. You probably need to look better at the destination. Use history
to get a list of previous issued commands.
A couple of things:
- As long as the target location is on the same partition as the source, no data will get moved. Only the name in the directory entry gets changed.
If there IS a move done ...
-
See
info coreutils 'mv invocation'
(on-line version https://www.gnu.org/software/coreutils/manual/html_node/mv-invocation.html#mv-invocation ) for how mv works and more specific this part:It first uses some of the same code that’s used by ‘cp -a’ to copy the requested directories and files, then (assuming the copy succeeded) it removes the originals. If the copy fails, then the part that was copied to the destination partition is removed. If you were to copy three directories from one partition to another and the copy of the first directory succeeded, but the second didn’t, the first would be left on the destination partition and the second and third would be left on the original partition.
-
So a move consists of 2 parts:
- a
cp -a
- a
mv
The removal part of the move is done AFTER there is confirmation the copy has been done correctly.
- a
-
if your mv consists of multiple files the copy and move are done in between. So a
mv a b c d e f /dir/
will do a
cp a /dir/ rm a ... cp f /dir/ rm f
so when there is a problem in between a and f it will have finished the move of a and up to where the problem appeared. This also applies to using wildcards.
Regarding the edit
sudo mv test.txt /ben
This moves test.txt to / and renames it to ben. And
ben.b@c-w46:~/Desktop/test-folder$ cd /ben
bash: cd: /ben: Not a directory
correctly errors out. Do a
ls -l /ben
and it will show the file.
What you always should do is append a / if you want to move a file to a directory.
sudo mv test.txt /ben/
would error out as /ben/ does not exists.