80

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)

  • 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. – 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

8 Answers8

104

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.

MatthewRock
  • 6,986
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.)

Dani_l
  • 4,943
  • "mv -n" doesn't work well in older GNU versions, see this alternative https://unix.stackexchange.com/a/700166/43233 – Noam Manos Apr 24 '22 at 08:05
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).

Majenko
  • 818
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

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...
Jakuje
  • 21,357
H Briceno
  • 21
  • 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). – Stéphane Chazelas Dec 11 '15 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.

Noam Manos
  • 1,031
1

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.

Kusalananda
  • 333,661
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

Noam Manos
  • 1,031