mv: déplacer le fichier uniquement si la destination n'existe pas

44

Puis - je utiliser mv file1 file2d'une manière qu'il ne se déplace file1à file2si file2n'existe pas?

J'ai essayé

yes n | mv -i file1 file2

(cela permet de mvdemander si le fichier 2 doit être remplacé et de répondre automatiquement non), mais en plus de l'abuser, -icela ne me donne pas non plus de codes d'erreur intéressants (toujours 141 au lieu de 0 s'il est déplacé et autre chose s'il n'est pas déplacé)

Fabian Schmitthenner
la source
3
Vous devez avoir l' pipefailoption 141, car le statut de sortie est défini yes, pas mvce qui n'aurait aucune raison d'obtenir un SIGPIPE ici.
Stéphane Chazelas
Cette approche échoue également si fichier2 est un répertoire (il déplacera fichier1 dans le répertoire fichier2). GNU mv a -Tpour cela.
Stéphane Chazelas
@ StéphaneChazelas Si vous souhaitez utiliser le statut de sortie de mvplutôt que celui de yes, la solution la plus simple pourrait êtremv -i file1 file2 < <(yes n)
kasperd

Réponses:

63

mv -vn file1 file2. Cette commande fera ce que vous voulez. Vous pouvez sauter -vsi vous voulez.

-v le rend verbeux - mv vous dira qu'il a déplacé le fichier s'il le déplace (utile, car il est possible que le fichier ne soit pas déplacé)

-n se déplace uniquement si le fichier 2 n'existe pas.

S'il vous plaît noter cependant que ce n'est pas POSIX comme mentionné par ThomasDickey .

MatthewRock
la source
2
Cependant, ce n'est pas POSIX .
Thomas Dickey
1
@ThomasDickey POSIX le supporte-t-il de manière atomique?
Fabian Schmitthenner
3
to @Fabian: Probablement pas, mais même dans les réponses suggérées, il est possible qu'une course éclate au sein des outils, en fonction de la manière dont ils sont écrits.
Thomas Dickey
3
cela semble ne pas être libre de toute race, cela stracemontre qu’il utilise (sur mon système): stat ("fichier2", 0x7ffe3e705d10) = -1 ENOENT (aucun fichier ou répertoire de ce type) lstat ("fichier1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("fichier2", 0x7ffe3e705a10) = -1 ENOENT (aucun fichier ou répertoire de ce type) renommer ("fichier1", "fichier2") = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (recherche illégale). Alors renommer semble être utilisé. La solution @ StéphaneChazelas semble être la bonne si vous voulez vraiment faire la course gratuitement.
Fabian Schmitthenner
2
Je me demande pourquoi il n'utilise pasrenameat2
Fabian Schmitthenner
16

mv -n

Depuis man mvun système GNU:

-n, --no-clobber
ne pas écraser un fichier existant

Sur un système FreeBSD:

-nNe pas écraser un fichier existant. (L'option -n remplace toutes les options -f ou -i précédentes.)

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

Ou:

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

Ne fonctionnerait que mvsi file2n'existe pas. Notez que cela ne garantit pas qu’un file2ne sera pas annulé car un file2aurait pu être créé entre le test et le mv, mais notez au moins que les versions actuelles de GNU mvavec -iou -nne donnent pas cette garantie non plus (bien que la condition de concurrence soit plus étroite là-bas puisque le contrôle est fait à l'intérieur mv).

De l’autre côté, il est portable, vous permet de discriminer les cas et fonctionne quel que soit le type de file2fichier (normal, canal, répertoire ).

Majenko
la source
3
cela introduit-il une condition de concurrence critique dans laquelle un fichier peut être écrit entre le contrôle d'existence et le déménagement?
Fabian Schmitthenner
3
Toujours une possibilité quoi que vous fassiez.
Majenko
3
L'API Linux a renameat2que vous pouvez donner un RENAME_NOREPLACEdrapeau. Je crois que cela vérifie de manière atomique l'existence du fichier, puis le déplace.
Fabian Schmitthenner
-d pour les répertoires, ou -l pour les liens, ou même -e pour tout type de fichier
Majenko
le changement de nom peut être libre de race, mais le reste de la commande mv ne l’est pas. S'il pense qu'il n'a pas besoin de dissocier le lien, le changement de nom échoue brusquement. Ce serait une erreur.
Majenko
8

Une approche sans course avec GNU lnfournie file1n’est pas du type répertoire :

ln -PT file1 file2 && rm file1

(Sauf pour les bogues dans certains systèmes de fichiers réseau), cela garantit qu'aucun file2fichier ne sera remplacé (ou que s'il file2est de type répertoire, file1il ne sera pas déplacé dans celui-ci), car l' link()appel système, contrairement à l' rename()appel système, échouera si la la cible existe.

Cependant, il y aura un état intermédiaire où le fichier existe en tant que file1et file2.

L' -Toption (toujours faire un link("file1", "file2")même si file2est de type répertoire) est spécifique à GNU.

Vous pouvez également utiliser la linkcommande:

link file1 file2 && rm file1

Toutefois, si file1est un lien symbolique, en fonction de la mise en œuvre, il file2s'agira soit d'un lien dur vers ce lien symbolique, soit vers la cible de ce lien symbolique (sous Solaris, utilisez /usr/sbin/linkou non /usr/xpg4/bin/link).

Stéphane Chazelas
la source
2
savez-vous si l'api linux renameat2avec drapeau RENAME_NOREPLACEest atomique?
Fabian Schmitthenner
1
@Fabian, AFAICT c'est censé mais c'est tout nouveau et n'est pas supporté par tous les systèmes de fichiers. À l'avenir, nous pouvons nous attendre à ce que les futures implémentations mv sur Linux l'utilisent. C'est ce pour quoi il a été conçu.
Stéphane Chazelas
0

Vous pouvez également utiliser test -e namequi renverra true si le nom existe (quels que soient le fichier, le répertoire ou le lien symbolique).

Par exemple:

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...
H Briceno
la source
1
Mais ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Même chose avec par exemple ln -s /var/spool/cron/crontabs/. exists(et vous n'êtes ni root ni membre du groupe crontab).
Stéphane Chazelas