mv: sposta il file solo se la destinazione non esiste


44

Posso usare mv file1 file2in un modo che si muove solo file1per file2se file2non esiste?

ho provato

yes n | mv -i file1 file2

(questo permette di mvchiedere se file2 deve essere sovrascritto e risponde automaticamente a no) ma oltre ad abusarlo -inon mi dà nemmeno dei bei codici di errore (sempre 141 invece di 0 se spostato e qualcos'altro se non spostato)


3
Devi avere l' pipefailopzione on poiché 141 sarebbe lo stato di uscita di yes, non mvche non avrebbe motivo di ottenere un SIGPIPE qui.
Stéphane Chazelas,

Questo approccio fallisce anche se file2 è una directory (sposta file1 nella directory file2). GNU mv ha una -Tper questo.
Stéphane Chazelas,

@ StéphaneChazelas Se si desidera utilizzare lo stato di uscita mvpiuttosto che quello di yes, la soluzione più semplice potrebbe esseremv -i file1 file2 < <(yes n)
kasperd,

Risposte:


63

mv -vn file1 file2. Questo comando farà quello che vuoi. Puoi saltare -vse vuoi.

-v lo rende dettagliato - mv ti dirà che ha spostato il file se lo sposta (utile, poiché è possibile che il file non venga spostato)

-n si sposta solo se file2 non esiste.

Si noti tuttavia che questo non è POSIX come indicato da ThomasDickey .


2
Tuttavia, non è POSIX .
Thomas Dickey,

1
@ThomasDickey POSIX lo supporta in modo atomico?
Fabian Schmitthenner,

3
a @Fabian: probabilmente no, ma anche all'interno delle risposte suggerite c'è la possibilità di una gara all'interno degli strumenti, a seconda di come sono scritti.
Thomas Dickey,

3
questo sembra non essere race free, stracemostra che utilizza (sul mio sistema): stat ("file2", 0x7ffe3e705d10) = -1 ENOENT (nessun file o directory simile) lstat ("file1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("file2", 0x7ffe3e705a10) = -1 ENOENT (nessun file o directory simile) rinomina ("file1", "file2") = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (ricerca illegale). Quindi la ridenominazione sembra essere usata. La soluzione @ StéphaneChazelas sembra essere quella giusta se vuoi davvero farlo senza gara.
Fabian Schmitthenner,

2
Mi chiedo perché non usirenameat2
Fabian Schmitthenner l'

16

mv -n

Da man mvun sistema GNU:

-n, --no-clobber
non sovrascrive un file esistente

Su un sistema FreeBSD:

-nNon sovrascrivere un file esistente. (L'opzione -n ​​sovrascrive qualsiasi opzione -f o -i precedente.)


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

O:

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

Funzionerebbe solo mvse file2non esiste. Nota che non garantisce che a file2non verrà sovrascritto perché file2avrebbe potuto essere creato a tra il test e il mv, ma nota che almeno le versioni correnti di GNU mvcon -io -nnon offrono tale garanzia (anche se le condizioni di gara sono più strette lì poiché il controllo viene eseguito all'interno mv).

Dall'altro lato, è portatile, consente di discriminare tra i casi e funziona indipendentemente dal tipo di file2file (normale, pipe, persino directory ).


3
questo introduce una condizione di competizione in cui un file potrebbe essere scritto tra il controllo dell'esistenza e lo spostamento?
Fabian Schmitthenner,

3
Sempre una possibilità qualunque cosa tu faccia.
Majenko,

3
API Linux ha renameat2cui puoi dare un RENAME_NOREPLACEflag. Credo che questo controlla atomicamente l'esistenza del file e quindi sposta il file.
Fabian Schmitthenner,

-d per le directory o -l per i collegamenti o anche -e per qualsiasi tipo di file
Majenko,

la ridenominazione può essere senza razza ma il resto del comando mv no. Se pensa di non aver bisogno di scollegare, improvvisamente la ridenominazione fallisce avrebbe (dovrebbe) errore.
Majenko,

8

Un approccio senza gara con GNU lnfornito file1non è di tipo directory :

ln -PT file1 file2 && rm file1

(Ad eccezione dei bug in alcuni file system di rete), che garantisce che nessun file2file verrà sovrascritto (o che se file2è di tipo directory, file1non verrà spostato in esso), perché la link()chiamata di sistema, contrariamente alla rename()chiamata di sistema, fallirà se il l'obiettivo esiste.

Tuttavia, ci sarà uno stato intermedio in cui il file esiste sia come file1e file2.

L' -Topzione (fare sempre una directory link("file1", "file2")anche se file2di tipo) è specifica per GNU.

Puoi anche usare il linkcomando:

link file1 file2 && rm file1

Tuttavia, se si file1tratta di un collegamento simbolico, a seconda dell'implementazione, file2sarà un collegamento reale a quel collegamento simbolico o alla destinazione di quel collegamento simbolico (su Solaris, usare /usr/sbin/link, non /usr/xpg4/bin/link).


2
sai se l'api di linux renameat2con flag RENAME_NOREPLACEè atomico?
Fabian Schmitthenner,

1
@Fabian, AFAICT è pensato ma è molto nuovo e non supportato per tutti i filesystem. In futuro, possiamo aspettarci che le future implementazioni mv su Linux lo utilizzino. Questo è quello per cui è stato progettato.
Stéphane Chazelas,

0

Puoi anche usare test -e namequale restituirà vero se il nome esiste (indipendentemente dal file, dalla directory o dal link simbolico).

Per esempio:

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
Ma ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Lo stesso vale per esempio ln -s /var/spool/cron/crontabs/. exists(e non sei root o membro del gruppo crontab).
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.