mv: mueve el archivo solo si el destino no existe

44

¿Puedo utilizar mv file1 file2de una manera que sólo se mueve file1a file2si file2que no existe?

He intentado

yes n | mv -i file1 file2

(esto permite mvpreguntar si el archivo 2 debe ser anulado y automáticamente responde que no), pero además de abusar -i, tampoco me da buenos códigos de error (siempre 141 en lugar de 0 si se movió y algo más si no se movió)

Fabian Schmitthenner
fuente
3
Debe tener la pipefailopción activada ya que 141 sería el estado de salida de yes, mvque no tendría ninguna razón para obtener un SIGPIPE aquí.
Stéphane Chazelas
Ese enfoque también falla si el archivo2 es un directorio (moverá el archivo1 al directorio del archivo2). GNU mv tiene un -Tpara eso.
Stéphane Chazelas
@ StéphaneChazelas Si lo que se desea es usar el estado de salida en mvlugar del de yes, la solución más simple podría sermv -i file1 file2 < <(yes n)
kasperd el

Respuestas:

63

mv -vn file1 file2. Este comando hará lo que quieras. Puedes saltarte -vsi quieres.

-v lo hace detallado: mv le dirá que movió el archivo si lo mueve (útil, ya que existe la posibilidad de que el archivo no se mueva)

-n se mueve solo si el archivo2 no existe.

Sin embargo, tenga en cuenta que esto no es POSIX como lo menciona ThomasDickey .

MatthewRock
fuente
2
Sin embargo, no es POSIX .
Thomas Dickey
1
@ThomasDickey ¿POSIX admite esto de una manera atómica?
Fabian Schmitthenner
3
a @Fabian: Probablemente no, pero incluso dentro de las respuestas sugeridas existe la posibilidad de una carrera dentro de las herramientas, dependiendo de cómo estén escritas.
Thomas Dickey
3
esto parece no estar libre de carrera, stracemuestra que usa (en mi sistema): stat ("file2", 0x7ffe3e705d10) = -1 ENOENT (No existe tal archivo o directorio) lstat ("file1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("file2", 0x7ffe3e705a10) = -1 ENOENT (No existe tal archivo o directorio) renombrar ("file1", "file2") = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (búsqueda ilegal). Así que parece que se usa cambiar el nombre. La solución @ StéphaneChazelas parece ser la correcta si realmente quieres hacerlo sin carreras.
Fabian Schmitthenner
2
Me pregunto por qué no se usarenameat2
Fabian Schmitthenner
16

mv -n

Desde man mvun sistema GNU:

-n, --no-clobber
no sobrescribe un archivo existente

En un sistema FreeBSD:

-nNo sobrescriba un archivo existente. (La opción -n anula cualquier opción -f o -i anterior).

Dani_l
fuente
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

Solo se ejecutaría mvsi file2no existe. Tenga en cuenta que no garantiza que file2no se anule a porque se file2podría haber creado entre la prueba y la prueba mv, pero tenga en cuenta que al menos las versiones actuales de GNU mvcon -io -nno dan esa garantía tampoco (aunque la condición de carrera es más estrecha allí ya que la verificación se realiza dentro mv).

Por otro lado, es portátil, le permite discriminar entre los casos y funciona independientemente del tipo de file2archivo (regular, canalización, incluso directorio ).

Majenko
fuente
3
¿Esto introduce una condición de carrera en la que se podría escribir un archivo entre la verificación de existencia y el movimiento?
Fabian Schmitthenner
3
Siempre una posibilidad, hagas lo que hagas.
Majenko
3
La API de Linux tiene a la renameat2que le puedes dar una RENAME_NOREPLACEbandera. Creo que esto comprueba atómicamente la existencia del archivo y luego lo mueve.
Fabian Schmitthenner
-d para directorios, o -l para enlaces, o incluso -e para cualquier tipo de archivo
Majenko
el cambio de nombre puede estar libre de carrera, pero el resto del comando mv no. Si cree que no necesita desvincularse, de repente el cambio de nombre falla (debería) error.
Majenko
8

Un enfoque libre de raza con GNU lnproporcionado file1no es del tipo directorio :

ln -PT file1 file2 && rm file1

(Excepto por errores en algunos sistemas de archivos de red), eso garantiza que ningún file2archivo se anulará (o que si file2es de tipo directorio, file1no se moverá a él), porque la link()llamada al sistema, al contrario de la rename()llamada del sistema, fallará si el El objetivo existe.

Sin embargo, habrá un estado intermedio donde el archivo existe tanto como file1y file2.

La -Topción (hacer siempre un directorio link("file1", "file2")aunque file2sea ​​de tipo) es específico de GNU.

También puedes usar el linkcomando:

link file1 file2 && rm file1

Sin embargo, si file1es un enlace simbólico, dependiendo de la implementación, file2será un enlace directo a ese enlace simbólico o al destino de ese enlace simbólico (en Solaris, use /usr/sbin/link, not /usr/xpg4/bin/link).

Stéphane Chazelas
fuente
2
¿Sabes si la API de Linux renameat2con bandera RENAME_NOREPLACEes atómica?
Fabian Schmitthenner
1
@Fabian, AFAICT está destinado a ser pero es muy nuevo y no es compatible con todos los sistemas de archivos. En el futuro, podemos esperar que futuras implementaciones de mv en Linux usen eso. Para eso fue diseñado.
Stéphane Chazelas
0

También puede usar test -e nameque devolverá verdadero si el nombre existe (independientemente del archivo, directorio o enlace simbólico).

Por ejemplo:

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 Briceño
fuente
1
Pero ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Lo mismo con por ejemplo ln -s /var/spool/cron/crontabs/. exists(y no eres root ni miembro del grupo crontab).
Stéphane Chazelas