79

Can I use mv file1 file2 in a way that it only moves file1 to file2 if file2 doesn't exist?

I've tried

yes n | mv -i file1 file2

(this lets mv ask if file2 should be overridden and automatically answer no) but besides abusing -i it also doesn't give me nice error codes (always 141 instead of 0 if moved and something else if not moved)

3
  • 5
    You must have the pipefail option on as 141 would be the exit status of yes, not mv which would have no reason to get a SIGPIPE here. Dec 10, 2015 at 13:05
  • That approach also fails if file2 is a directory (it will move file1 into the file2 directory). GNU mv has a -T for that. Dec 10, 2015 at 13:14
  • @StéphaneChazelas If the desire is to use the exit status of mv rather than that of yes, the simplest solution might be mv -i file1 file2 < <(yes n)
    – kasperd
    Dec 11, 2015 at 10:02

8 Answers 8

102

mv -vn file1 file2. This command will do what you want. You can skip -v if you want.

-v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved)

-n moves only if file2 does not exist.

Please note however, that this is not POSIX as mentioned by ThomasDickey.

10
  • 3
    However, it's not POSIX. Dec 10, 2015 at 13:13
  • 1
    @ThomasDickey does POSIX support this in an atomic way at all? Dec 10, 2015 at 13:15
  • 4
    to @Fabian: Probably not, but even within the suggested answers there's the possibility of a race within the tools, depending how they are written. Dec 10, 2015 at 13:18
  • 4
    this seems not to be race free, strace shows that it uses (on my system): stat("file2", 0x7ffe3e705d10) = -1 ENOENT (No such file or directory) lstat("file1", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 lstat("file2", 0x7ffe3e705a10) = -1 ENOENT (No such file or directory) rename("file1", "file2") = 0 lseek(0, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek). So rename seems to be used. @StéphaneChazelas solution seems to be the right one if you really want to do it race free. Dec 11, 2015 at 21:02
  • 2
    I wonder why it doesn't use renameat2 Dec 11, 2015 at 21:06
19

mv -n

From man mv on a GNU system:

-n, --no-clobber
do not overwrite an existing file

On a FreeBSD system:

-n Do not overwrite an existing file. (The -n option overrides any previous -f or -i options.)

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

Or:

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

Would only run mv if file2 doesn't exist. Note that it does not guarantee that a file2 won't be overridden because a file2 could have been created between the test and the mv, but note that at least current versions of GNU mv with -i or -n don't give that guarantee either (though the race condition is narrower there since the check is done within mv).

On the other end, it is portable, allows you to discriminate between the cases, and works regardless of the type of the file2 file (regular, pipe, even directory).

7
  • 4
    does this introduce a race condition where a file could be written between the existence check and the move? Dec 10, 2015 at 13:02
  • 3
    Always a possibility whatever you do.
    – Majenko
    Dec 10, 2015 at 13:02
  • 3
    Linux API has renameat2 which you can give a RENAME_NOREPLACE flag. I believe this atomically checks file existence and then moves the file. Dec 10, 2015 at 13:07
  • -d for directories, or -l for links, or even -e for any file type
    – Majenko
    Dec 10, 2015 at 13:07
  • the rename may be race free but the rest of the mv command isn't. If it thinks it doesn't need to unlink then suddenly the rename fails it would (should) error.
    – Majenko
    Dec 10, 2015 at 13:09
9

A race-free approach with GNU ln provided file1 is not of type directory:

ln -PT file1 file2 && rm file1

(Except for bugs in some network file systems), that guarantees that no file2 file will get overridden (or that if file2 is of type directory, file1 will not be moved into it), because the link() system call, contrary to the rename() system call will fail if the target exists.

However, there will be an intermediate state where the file exists both as file1 and file2.

The -T option (to always do a link("file1", "file2") even if file2 is of type directory) is GNU-specific.

You could also use the link command:

link file1 file2 && rm file1

However, if file1 is a symlink, depending on the implementation, file2 will be either a hardlink to that symlink or to the target of that symlink (on Solaris, use /usr/sbin/link, not /usr/xpg4/bin/link).

2
  • 2
    do you know if the linux api renameat2 with flag RENAME_NOREPLACE is atomic? Dec 11, 2015 at 21:19
  • 1
    @Fabian, AFAICT it's meant to but it's very new and not supported for all filesystems. Going forward, we can expect future mv implementations on Linux to use that. That's what it was designed for. Dec 11, 2015 at 21:46
2

You can also use test -e name which will return true if the name exists (regardless of file, directory or symlink).

For example:

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
  • 1
    But ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Same with for instance ln -s /var/spool/cron/crontabs/. exists (and you're not root or member of the crontab group). Dec 11, 2015 at 10:54
2

To move only if destination doesn't exist, but also if source exists:

[[ ! -e src ]] || [[ -e dest ]] || mv src dest

Using ! NOT || OR is safer when running bash script with set -e, since it will not break the script with a non-zero exit code, like [[ false ]] && ... can.

0

This answer assumes that mv might not have the non-standard -n (or --no-clobber) option, as is the case on e.g. OpenBSD, NetBSD, and AIX.

Using mv -i will trigger an interaction if the destination file exists. This allows the user to avoid overwriting the destination by answering affirmatively.

If the user does not answer at all, mv will not overwrite the destination and will exit with a zero exit status:

mv -i file1 file2 </dev/null

The fact that the pipeline shown in the question exits with a non-zero exit status indicates that the pipefail shell option is set in the bash shell.

The command in this answer would exit with a zero exit status in the same scenario, regardless of the state of the pipefail shell option. It would not exit with a non-zero status since no error was encountered.

To determine whether the file was renamed or not, a simple extra test could be added:

mv -i file1 file2 </dev/null && [ ! -e file1 ]

This would exit with a non-zero exit status if the file was not renamed, or if mv failed for some other reason.

0

mv -n (no-clobber) might not work properly in older GNU versions:

$ mv --version
mv (GNU coreutils) 8.22

$ pwd
/home/user

$ mv -n ./myfile /home/user/myfile
mv: './myfile' and '/home/user/myfile' are the same file

$ echo $?
1

To workaround this, check if FILE1 and FILE2 have the same device and inode numbers with -ef, before moving the file:

$ [[ ./myfile -ef /home/user/myfile ]] || mv ./myfile /home/user/myfile

$ echo $?
0
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .