昨天晚上发生了一件悲剧的事情,写了一下午的代码在mv过程当中被删除了。事情经过是这样子的,创建好Git仓库之后,准备把代码文件mv进对应的文件夹,原本目录的样子
$ tree . |-- a |-- a.c
就这两个简单的文件,所以我直接用
$ mv a*
大家可以看到这个命令没有敲完,因为后面还缺destination directory,然后我就切换到那个目录看了一眼路径,回来的时候鬼使神差的没有把路径敲上去就让这个命令执行了,然后惨剧就发生了,这个命令没有出错,我有种预感出事情了,但是这个时候心理还是期望a.c把a给覆盖了,但是ls看了下文件大小就觉得完了,源文件被覆盖了,因为mv没有撤回的命令,所以知道麻烦来了,网络上有人说这个时候可以umount被删文件所在的分区,然后用root用户对该分区进行grep,当然关键字就是被删文件当中有的关键字,越特殊越好,我有试过这方法,但是运气没那么好,只搜出来那个文件的名字,没有内容。想想算了,自己再重写一遍吧,源文件生成的可执行程序还在,里面还有些打印的文本信息,根据这些文本信息来重新写也还行,然后花了2个多小时,到凌晨2点,终于算写完了,功能也和侥幸留下的那个可执行文件一样。于是就睡觉了。
但是我在想mv是如何解析”a*”这个字串,然后把文件覆盖掉的呢?
早上起来看了下mv程序的代码
n_files = argc - optind; // optind是排除前面program name和options之后的参数索引, // 也就是被操作的文件或者文件夹 file = argv + optind;
我们都知道argv[0]是program name,所以”a*”这种情况optind就为1,于是
$ mv a*
就被解释成了
$ mv a a.c
所以当然a.c就被a给覆盖了
那为什么mv在覆盖的时候没有给予提示,因为在Linux/Unix的世界里面,它默认你应该知道你在执行什么操作。比如这个命令,移动A文件到B文件,这是再正常正确不过的一种操作,它就是认为你是要用A把B给覆盖的。
当然”a*”变成a和a.c这纯粹是一种巧合,因为一旦你要操作的文件(夹)数目大于2,它就会去检测最后一个是否是文件夹,如果是的,就把前面的文件(夹)移动到最后一个文件夹里面,如果不是,就告诉你输入错了。
if (target_directory_operand (file[n_files - 1])) target_directory = file[--n_files]; else if (2 < n_files) error (EXIT_FAILURE, 0, _("target %s is not a directory"), quote (file[n_files - 1])); if (target_directory) { int i; ok = true; for (i = 0; i < n_files; ++i) ok &= movefile (file[i], target_directory, true, &x); } else ok = movefile (file[0], file[1], false, &x);
所以如果我当前目录有2个以上的a打头的文件,那么我执行”mv a*”,它就会给出提示,或者只是说把文件挪动了一个位置,并不会覆盖。
好了,如果你担心以后在执行cp和mv的时候文件会给覆盖的话,你可以加”-i”参数,这样碰到覆盖的情况它就会征求你的意见,当然我们这么做只是为了避免像如上这个例子的巧合和意外。你始终还是要知道你在执行什么操作。
方便起见,你可以在.bashrc当中添加如下配置就可以。
# some interactive reminders alias mv='mv -i' alias cp='cp -i'
为什么rm不加,因为你要知道rm就是要把你的资料弄没的,所以要万分小心!
当然如果你知道你要用VCS来管理你的东西,那么一开始就用吧!