“mv *”如何知道要移动到哪里?

“mv *”如何知道要移动到哪里?

我有一个像这样的目录结构:

ravikumar@ravikumar-RV409-RV509-RV709:~$ tree test
test
|
├── 1
├── 2
├── 3
├── 4
├── a
├── b
└── c

我执行了 mv 命令如下:

ravikumar@ravikumar-RV409-RV509-RV709:~$ cd test/
ravikumar@ravikumar-RV409-RV509-RV709:~/test$ mv *

结果:

ravikumar@ravikumar-RV409-RV509-RV709:~/test$ tree
.        
|
└── c
    ├── 1
    ├── 2
    ├── 3
    ├── 4
    ├── a
    └── b

我的问题是 mv 如何判断将所有目录和文件移动到最后一个目录?那就是c

答案1

当你在该目录中运行它时,mv *相当于mv 1 2 3 4 a b c。除非-t存在选项,否则mv将最后一个非选项参数视为目标的名称,从而将1234ab全部移动到 中c

贝壳扩展 *进入文件名列表。发生这种情况时,您正在运行的命令实际上看不到*,而只能看到它扩展后的内容。因此您运行的命令不能根据您实际输入的内容判断如何处理它的参数——它只能看到 shell 传递给它的内容。

当你运行mv *(你可能已经推断,这几乎总是应该避免的)时,shell 和不是mv命令本身扩展*按字母顺序排列当前目录中包含的所有文件和目录的列表,不包括名称以 开头的文件和目录.

dotglob(您可以通过、、nullglobnocaseglobshell 选项以及变量自定义其工作方式GLOBIGNORE。请参阅文档了解详情。

如果c不是目录,您的命令将失败,不移动任何内容,并显示此错误消息:

mv: target 'c' is not a directory

但由于c是一个目录,它只是“工作”,即使——如果你运行该命令是为了除查看它会做什么之外的任何其他目的——你大概旨在在命令末尾写入单独目录的名称*

为了更直观地说明为什么mv无法知道它的参数是从扩展而来的*,假设你这样写道:

mv 1 c

那将移动1c。但也许您希望将两个项目移动到c

mv 1 2 c

或者五项:

mv 1 2 3 4 a c

或者六项:

mv 1 2 3 4 a b c

但 shell 确保mv *通过了完全相同的参数,相同的顺序执行mv如下命令mv 1 2 3 4 a b c

无法通过一次调用 来移动多个项目通常会很不方便mv。但即使这不被允许并且总是产生错误,您仍然可以在仅包含两个条目的目录中创建类似的混乱情况。然后 mv *在该目录中运行仍会尝试将一个移动到另一个。

如果你想知道 shell 会将某些内容扩展为什么,你可以将你感兴趣的命令替换为printf '%s\n',它会打印传递给它的所有后续参数,并以换行符分隔。例如:

printf '%s\n' *

答案2

这个答案增加了一些提示(但我认为主要答案是@Eliah Kagan 的答案)。


我创建了两个别名,以使事情更安全一些。我将它们存储在~/.bashrc其他别名附近。

alias mv='mv -i'
alias rm='rm -i'

这些别名使命令具有交互性,它们会询问您是否要覆盖/删除。

mv会询问您是否要覆盖目标、目标是否存在且为文件,或者目标是否为目录且其中已存在同名文件。这将帮助您避免错误,例如当您使用通配符时,mv *.


请注意

  • 这些别名仅适用于您自己的用户 ID。它们不适用于sudo。如果您希望在之后扩展别名sudo,您必须单独设置。如果您希望某个别名在 root shell 中起作用(就像您从sudo -s或获得的sudo -i那样),那么您必须创建一个类似的别名并将其存储在 中/root/.bashrc

  • 您可以使用前缀 'backslash'覆盖别名(并获取mv和的原始行为),rm

    \rm filename
    \mv filename1 filename2
    
  • 你可能会习惯于-i自动传递标志,并且虚假的安全感,例如在运行另一个系统时,这些别名不活动。

相关内容