Lesson Learn之文件被mv给覆盖

昨天晚上发生了一件悲剧的事情,写了一下午的代码在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来管理你的东西,那么一开始就用吧!

Leave a Reply

Your email address will not be published. Required fields are marked *