mv: mover arquivo somente se o destino não existir

44

Posso usar mv file1 file2de uma forma que só se move file1para file2se file2não existe?

eu tentei

yes n | mv -i file1 file2

(isso permite mvperguntar se o arquivo2 deve ser substituído e responder automaticamente não), mas além de abusar -i, também não me fornece códigos de erro agradáveis ​​(sempre 141 em vez de 0 se movido e outra coisa se não for movido)

Fabian Schmitthenner
fonte
3
Você deve ter a pipefailopção como 141, que seria o status de saída yes, e não mvteria razão para obter um SIGPIPE aqui.
Stéphane Chazelas
Essa abordagem também falha se file2 for um diretório (ele moverá file1 para o diretório file2). O GNU mv tem um -Tpara isso.
Stéphane Chazelas
@ StéphaneChazelas Se o desejo é usar o status de saída mve não a de yes, a solução mais simples poderia sermv -i file1 file2 < <(yes n)
kasperd

Respostas:

63

mv -vn file1 file2. Este comando fará o que você deseja. Você pode pular -vse quiser.

-v torna detalhado - o mv dirá que ele moveu o arquivo se o mover (útil, pois existe a possibilidade de o arquivo não ser movido)

-n move somente se o arquivo2 não existir.

Observe, no entanto, que isso não é POSIX, como mencionado por ThomasDickey .

MatthewRock
fonte
2
No entanto, não é POSIX .
Thomas Dickey
1
@ThomasDickey o POSIX suporta isso de uma maneira atômica?
Fabian Schmitthenner
3
para @Fabian: Provavelmente não, mas mesmo dentro das respostas sugeridas, há a possibilidade de uma corrida dentro das ferramentas, dependendo de como elas são escritas.
Thomas Dickey
3
isso parece não ser livre de raça, stracemostra que ele usa (no meu sistema): stat ("arquivo2", 0x7ffe3e705d10) = -1 ENOENT (nenhum arquivo ou diretório desse tipo) lstat ("arquivo1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("arquivo2", 0x7ffe3e705a10) = -1 ENOENT (não existe esse arquivo ou diretório) renomear ("arquivo1", "arquivo2") = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (busca ilegal). Portanto, renomear parece ser usado. A solução @ StéphaneChazelas parece ser a correta, se você realmente quiser fazê-lo sem correr.
Fabian Schmitthenner
2
Eu me pergunto por que ele não usarenameat2
Fabian Schmitthenner
16

mv -n

A partir man mvde um sistema GNU:

-n, --no-clobber
não substitui um arquivo existente

Em um sistema FreeBSD:

-nNão substitua um arquivo existente. (A opção -n substitui qualquer opção -f ou -i anterior.)

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

Seria executado apenas mvse file2não existir. Observe que não garante que a file2não seja substituída porque a file2poderia ter sido criada entre o teste e a mv, mas observe que pelo menos as versões atuais do GNU mvcom -iou -nnão oferecem essa garantia (embora a condição de corrida seja mais estreita desde que a verificação seja feita dentro mv).

Na outra ponta, ele é portátil, permite discriminar entre os casos, e funciona independentemente do tipo do file2arquivo (regular, tubulação, mesmo diretório ).

Majenko
fonte
3
isso introduz uma condição de corrida em que um arquivo pode ser gravado entre a verificação da existência e a mudança?
Fabian Schmitthenner
3
Sempre uma possibilidade, faça o que fizer.
Majenko
3
A API do Linux possui renameat2uma RENAME_NOREPLACEbandeira. Eu acredito que isso verifica atomicamente a existência do arquivo e depois move o arquivo.
Fabian Schmitthenner
-d para diretórios, ou -l para links, ou -e para qualquer tipo de arquivo #
Majenko
a renomeação pode ser livre de corrida, mas o restante do comando mv não. Se ele acha que não precisa desvincular, de repente, a renomeação falha, com erro (deveria).
Majenko
8

Uma abordagem sem corrida com o GNU lnfornecido file1não é do tipo diretório :

ln -PT file1 file2 && rm file1

(Exceto para erros em alguns sistemas de arquivos de rede), que garante que nenhum file2arquivo será substituído (ou que, se file2for do tipo diretório, file1não será movido para ele), porque a link()chamada do sistema, ao contrário da rename()chamada do sistema, falhará se o existe o alvo.

No entanto, haverá um estado intermediário em que o arquivo existe como file1e file2.

A -Topção (para fazer sempre um diretório link("file1", "file2")mesmo que file2seja do tipo) é específica do GNU.

Você também pode usar o linkcomando:

link file1 file2 && rm file1

No entanto, se file1houver um link simbólico, dependendo da implementação, file2será um link físico para esse link simbólico ou para o destino desse link simbólico (no Solaris, use /usr/sbin/link, não /usr/xpg4/bin/link).

Stéphane Chazelas
fonte
2
você sabe se o linux api renameat2com flag RENAME_NOREPLACEé atômico?
Fabian Schmitthenner
1
@Fabian, AFAICT, mas é muito novo e não é compatível com todos os sistemas de arquivos. No futuro, podemos esperar que futuras implementações de MV no Linux usem isso. Foi para isso que foi projetado.
Stéphane Chazelas
0

Você também pode usar o test -e nameque retornará true se o nome existir (independentemente do arquivo, diretório ou link simbólico).

Por exemplo:

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
fonte
1
Mas ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". O mesmo acontece com, por exemplo ln -s /var/spool/cron/crontabs/. exists(e você não é root ou membro do grupo crontab).
Stéphane Chazelas