42

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
    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. – Stéphane Chazelas Dec 10 '15 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. – Stéphane Chazelas Dec 10 '15 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 '15 at 10:02
58

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.

  • 2
    However, it's not POSIX. – Thomas Dickey Dec 10 '15 at 13:13
  • 1
    @ThomasDickey does POSIX support this in an atomic way at all? – Fabian Schmitthenner Dec 10 '15 at 13:15
  • 3
    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. – Thomas Dickey Dec 10 '15 at 13:18
  • 3
    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. – Fabian Schmitthenner Dec 11 '15 at 21:02
  • 2
    I wonder why it doesn't use renameat2 – Fabian Schmitthenner Dec 11 '15 at 21:06
16

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.)

10
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).

  • 3
    does this introduce a race condition where a file could be written between the existence check and the move? – Fabian Schmitthenner Dec 10 '15 at 13:02
  • 3
    Always a possibility whatever you do. – Majenko Dec 10 '15 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. – Fabian Schmitthenner Dec 10 '15 at 13:07
  • -d for directories, or -l for links, or even -e for any file type – Majenko Dec 10 '15 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 '15 at 13:09
8

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
    do you know if the linux api renameat2 with flag RENAME_NOREPLACE is atomic? – Fabian Schmitthenner Dec 11 '15 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. – Stéphane Chazelas Dec 11 '15 at 21:46
0

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
    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). – Stéphane Chazelas Dec 11 '15 at 10:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.