mv:仅在目标不存在时移动文件


44

我可以使用mv file1 file2的一种方式,它只能移动file1file2,如果file2不存在?

我试过了

yes n | mv -i file1 file2

(这可以mv询问是否应该重写file2并自动回答“否”),但是除了滥用-i它之外,它也不会给我带来很好的错误代码(如果移动了,则总是141而不是0;如果不移动则是其他东西)


3
您必须pipefail选择141,因为退出状态为yes,否则mv将没有理由在此处获取SIGPIPE。
斯特凡Chazelas

如果file2是目录,该方法也将失败(它将file1移到file2目录中)。GNU mv -T为此。
斯特凡Chazelas

@StéphaneChazelas如果想要使用mv而不是的退出状态yes,最简单的解决方法可能是mv -i file1 file2 < <(yes n)
kasperd

Answers:


63

mv -vn file1 file2。该命令将执行您想要的操作。您可以-v根据需要跳过。

-v 使其变得冗长-如果移动文件,mv会告诉您它已移动文件(很有用,因为有可能文件不会被移动)

-n 仅在file2不存在时移动。

但是请注意,这不是POSIX通过ThomasDickey提及


2
但是,它不是POSIX
Thomas Dickey

1
@ThomasDickey POSIX是否完全支持原子方式?
Fabian Schmitthenner,2015年

3
to @Fabian:可能不是,但是即使在建议的答案范围内,工具内也可能出现种族冲突,这取决于它们的编写方式。
Thomas Dickey 2015年

3
这似乎并非没有种族限制,strace表明它使用了(在我的系统上):stat(“ file2”,0x7ffe3e705d10)= -1 ENOENT(没有这样的文件或目录)lstat(“ file1”,{st_mode = S_IFREG | 0644, st_size = 0,...})= 0 lstat(“ file2”,0x7ffe3e705a10)= -1 ENOENT(无此类文件或目录)rename(“ file1”,“ file2”)= 0 lseek(0,0,SEEK_CUR) = -1 ESPIPE(非法搜索)。因此,似乎使用了重命名。如果您真的想免费比赛,@StéphaneChazelas解决方案似乎是正确的选择。
Fabian Schmitthenner,2015年

2
我不知道为什么它不使用renameat2
Fabian Schmitthenner 2015年

16

mv -n

man mv一个GNU系统上:

-n,-no-clobber
不会覆盖现有文件

在FreeBSD系统上:

-n不要覆盖现有文件。(-n选项将覆盖以前的任何-f或-i选项。)


10
if [ ! -e file2 ] && [ ! -L file2 ]
then
    mv file1 file2
# else echo >&2 there is already a file2 file.
fi

要么:

if ! ls -d file2 > /dev/null 2>&1
then
    mv file1 file2
fi

只运行mv,如果file2不存在。请注意,它不能保证file2不会覆盖a file2,因为在测试和之间可能已经创建了a mv,但是请注意,至少当前版本的GNU mv带有-i-n不提供保证(尽管竞争条件较窄)因为检查是在mv)中完成的。

另一方面,它是可移植的,可让您区分大小写,并且不管file2文件的类型(常规,管道,甚至目录)如何工作。


3
这是否引入了一种竞争条件,可以在存在性检查和移动之间写入文件?
Fabian Schmitthenner 2015年

3
无论您做什么,总是有可能。
Majenko

3
Linux API renameat2可以提供一个RENAME_NOREPLACE标志。我相信这会自动检查文件是否存在,然后移动文件。
Fabian Schmitthenner,2015年

-d表示目录,-l表示链接,甚至-e表示任何文件类型
Majenko 2015年

重命名可能是不分种族的,但是mv命令的其余部分不是。如果它认为不需要取消链接,则突然重命名失败,将(应该)出错。
Majenko

8

与GNU的免费比赛的方法ln提供file1的类型是不目录

ln -PT file1 file2 && rm file1

(除了在一些网络文件系统错误),这保证没有file2文件将得到覆盖(或者,如果file2是目录类型的,file1不会被移动到它),因为link()系统调用,违背了rename()系统调用将失败,如果目标存在。

然而,将存在该文件存在既作为中间状态file1file2

-T选项(link("file1", "file2")即使file2是目录类型,也始终执行一次)是特定于GNU的。

您还可以使用以下link命令:

link file1 file2 && rm file1

但是,如果file1是符号链接,则根据实现的不同,file2它将是指向该符号链接的硬链接或指向该符号链接的目标的硬链接(在Solaris上,请使用/usr/sbin/link,而不是/usr/xpg4/bin/link)。


2
您知道renameat2带有标志的linux api RENAME_NOREPLACE是否为atomic吗?
Fabian Schmitthenner,2015年

1
@Fabian,本来是AFAICT的意思,但它是非常新的,并非所有文件系统都支持。展望未来,我们可以预期Linux上将来的mv实现会使用它。这就是它的设计目的。
斯特凡Chazelas

0

test -e name如果名称存在(无论文件,目录或符号链接如何),也可以使用which返回true。

例如:

touch file
mkdir dir
ln -s file symlink
test -e file && echo file exists
test -e dir && echo dir exists
test -e symlink && echo symlink exists
test -e file || echo you wont see this echo
test -e doesnotexist || echo doesnotexist does not exist...

1
但是ln -s doesnotexist exists; test -e exists || echo "does it really not exist?"。与例如相同ln -s /var/spool/cron/crontabs/. exists(并且您不是root或crontab组的成员)。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.