将文件意外移动到不存在的目录会擦除文件吗?

ben*_*sky 9 command-line files mv

我从一个目录中移动了所有文件mv,但不小心在目标位置路径中打错了字。

系统返回一条消息,该目录不存在,但我的源目录中的文件已被删除。

这是一个错误吗?将文件移动到不存在的位置是否应该删除正在移动的文件?(这是在 Ubuntu 18.04.2 LTS 上。)


具体是:

  1. 创建的test.txt文件。
  2. 移动文件/bensudo
  3. 文件消失了。/ben不存在。

命令和输出是:

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
Run Code Online (Sandbox Code Playgroud)

Eli*_*gan 14

在你实际运行的命令中,你没有丢失任何东西!它成功地重命名test.txt/ben. 假设test.txt是一个常规文件,那么新文件也是如此/ben(毕竟它们是同一个文件)。

你看到的原因bash: cd: /ben: Not a directory是它在罐头上写的:/ben不是目录。您仍然可以访问该文件。

如果您想避免这种错误并mv在目标不是目录时强制失败,请/在其上写一个结尾或使用-t dir. 例如,其中任何一个都可以防止您遇到的(非常小的!)问题(sudo必要时):

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

关于您的问题中描述的一般情况的信息——关于通过尝试移动文件而丢失文件——以及可能出错和不可能出错的信息。


正如Rinzwind 所说,当您尝试使用单个mv命令将文件移动到不存在的目标目录时,不应导致数据丢失。但是,如果您运行mv多次(例如在 shell 循环中),则可能会发生这种情况。

例如,假设我有:

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
Run Code Online (Sandbox Code Playgroud)

所有这些文件移动到dest,我应该通过所有他们的名字mv,像一个命令mv file*.txt dest/mv file*.txt dest。在任何一种情况下——也就是说,无论我是否用斜杠写目标目录名称——这都是正确的。在任何一种情况下,如果我拼错了目标目录名称(例如,通过写入dst),我会收到错误mv: target 'dst' is not a directory并且不会丢失任何数据。

但是,假设我拼错了dst,省略了尾随/,并运行了多个mv命令。那会很糟糕,因为当目标mv是一个普通文件时,mv 替换它!

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!
Run Code Online (Sandbox Code Playgroud)

这就是为什么很多人总是喜欢与尾部写入目标目录/mv

ek@Apok:~/tmp$ mv file03.txt dst/
mv: failed to access 'dst/': Not a directory
Run Code Online (Sandbox Code Playgroud)

您可以使用mv -i在覆盖之前询问您,或者mv -n静默覆盖。否则,mv仅在覆盖之前询问您目标是否为只读文件。考虑这一点的一个原因是它涵盖了其他情况,例如mv file01.txt dest/您没有意识到dest/file01.txt存在并且不想覆盖它的情况。

你也可以在命令的末尾使用-t dest而不是写dest,例如,mv -t dest file*.txt。如果dest是常规文件,这将拒绝操作,无论您是否编写了尾随/.


使用自动化机制来运行多个此类命令可能会严重加剧问题。例如,正如所写的命令for f in file*.txt; do mv "$f" dest/; done是不必要的复杂但安全,因为如果我不小心指定了文件dst而不是目录dest(但保留了斜杠!),它会给我mv: failed to access 'dst/': Not a directory每个文件一个错误。但是,如果我省略了结尾的/,那么它会将每个文件重命名为dst替换之前的dst,并且只会保留最后一个文件。

使用 可以实现类似的不良结果find,包括在可能合理使用的情况下find(但不同,但仍需格外小心)。例如,假设我想将file*.txt整个目录树(除了dest它本身)中与 glob 匹配的所有文件移动到目录dest. 我可能首先想到使用这个:

find . -path ./dest -prune -o -name 'file*.txt' -exec mv {} dest/ \;  # bad, don't use
Run Code Online (Sandbox Code Playgroud)

因为我包含了一个尾随/, 写作dest/而不是destdst即使我写dst而不是dest. 但是它有一个相关的问题,它会覆盖它已经复制的文件,如果目录树不同部分的文件具有相同的名称。例如,如果有一个a/file01.txt和一个b/file01.txt,一个会覆盖另一个。为了避免这种情况,最好使用这样的东西:

find -path ./dest -prune -o -name 'file*.txt' -exec mv -it dest/ {} \;  # okay
Run Code Online (Sandbox Code Playgroud)

另一个好处-t dir是,因为它允许您在移动项目之前指定目标目录,所以它与 的+形式兼容-exec,其中将多个项目传递给一个命令,从而运行更少的命令(通常只有一个):

find -path ./dest -prune -o -name 'file*.txt' -exec mv -it dest/ {} +  # good
Run Code Online (Sandbox Code Playgroud)

在这两种情况下(除了\;vs.之外,它们是相同的+)我还传递了-i在每个会覆盖文件的操作之前进行提示的选项。如果你只想默默地跳过那些,写n而不是i. 如果您想先测试您的find命令,您可以在命令的其余部分echo之后-exec但之前编写以打印将运行的内容。例如:

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
Run Code Online (Sandbox Code Playgroud)

(当然,那是在我展示的原始目录中,所有要移动的文件都在同一位置,因此哪里find是矫枉过正,最复杂的合理使用命令是mv -it dest/ file*.txt。)


Rin*_*ind 8

不,你的建议应该(!)不可能。您可能需要更好地查看目的地。使用history可获取前发出的命令的列表。

几件事:

  • 只要目标位置与源位置在同一分区上,就不会移动任何数据。仅更改目录条目中的名称。

如果有动作完成...

  • 请参阅info coreutils 'mv invocation'(在线版本https://www.gnu.org/software/coreutils/manual/html_node/mv-invocation.html#mv-invocation)了解 mv 的工作原理以及更具体的这一部分:

    它首先使用一些与 'cp -a' 使用的代码相同的代码来复制请求的目录和文件,然后(假设复制成功)删除原始文件。如果复制失败,则删除复制到目标分区的部分。如果您要将三个目录从一个分区复制到另一个分区,并且第一个目录的复制成功,但第二个没有成功,则第一个将保留在目标分区上,第二个和第三个将保留在原始分区上。

  • 所以一个动作由两部分组成:

    1. 一种 cp -a
    2. 一种 mv

    移动的移除部分在确认复制已正确完成后完成。

  • 如果您的 mv 包含多个文件,则复制和移动在两者之间完成。所以一个

    mv a b c d e f /dir/
    
    Run Code Online (Sandbox Code Playgroud)

    会做一个

    cp a /dir/
    rm a
    ...
    cp f /dir/
    rm f
    
    Run Code Online (Sandbox Code Playgroud)

    因此,当 a 和 f 之间出现问题时,它将完成 a 的移动,直到出现问题的位置。这也适用于使用通配符。


关于编辑

sudo mv test.txt /ben
Run Code Online (Sandbox Code Playgroud)

这会将 test.txt 移动到 / 并将其重命名为 ben。和

ben.b@c-w46:~/Desktop/test-folder$ cd /ben
bash: cd: /ben: Not a directory
Run Code Online (Sandbox Code Playgroud)

正确错误。做一个

ls -l /ben
Run Code Online (Sandbox Code Playgroud)

它会显示文件。

如果要将文件移动到目录,则始终应该做的是附加 /。

sudo mv test.txt /ben/
Run Code Online (Sandbox Code Playgroud)

会出错,因为 /ben/ 不存在。