mv: Przenieś plik tylko wtedy, gdy miejsce docelowe nie istnieje

44

Czy mogę używać mv file1 file2w taki sposób, że porusza się tylko file1na file2razie file2nie istnieje?

próbowałem

yes n | mv -i file1 file2

(pozwala to mvzapytać, czy plik2 powinien zostać zastąpiony i automatycznie odpowiedzieć nie), ale oprócz nadużywania -inie daje mi również ładnych kodów błędów (zawsze 141 zamiast 0, jeśli przeniesiono i coś innego, jeśli nie przeniesiono)

Fabian Schmitthenner
źródło
3
Musisz mieć pipefailwłączoną opcję, ponieważ 141 oznacza status wyjścia yes, a nie mvktóry nie miałby powodu, aby uzyskać SIGPIPE tutaj.
Stéphane Chazelas,
Takie podejście kończy się niepowodzeniem również wtedy, gdy plik2 jest katalogiem (przenosi plik1 do katalogu plik2). GNU mv ma coś -Ttakiego.
Stéphane Chazelas,
@ StéphaneChazelas Jeśli pragniesz użyć statusu wyjścia mvzamiast statusu yes, najprostszym rozwiązaniem może byćmv -i file1 file2 < <(yes n)
kasperd

Odpowiedzi:

63

mv -vn file1 file2. To polecenie zrobi, co chcesz. Możesz pominąć, -vjeśli chcesz.

-v sprawia, że ​​jest pełny - mv powie ci, że przeniósł plik, jeśli go przeniesie (przydatne, ponieważ istnieje możliwość, że plik nie zostanie przeniesiony)

-n przesuwa się tylko wtedy, gdy plik2 nie istnieje.

Pamiętaj jednak, że nie jest to POSIX, jak wspomniał ThomasDickey .

MatthewRock
źródło
2
Nie jest to jednak POSIX .
Thomas Dickey,
1
@ThomasDickey czy POSIX w ogóle obsługuje to w sposób atomowy?
Fabian Schmitthenner,
3
do @Fabian: Prawdopodobnie nie, ale nawet w obrębie sugerowanych odpowiedzi istnieje możliwość wyścigu w obrębie narzędzi, w zależności od tego, jak są napisane.
Thomas Dickey,
3
wydaje się, że nie jest wolne od wyścigów, stracepokazuje, że używa (w moim systemie): stat ("plik2", 0x7ffe3e705d10) = -1 ENOENT (Brak takiego pliku lub katalogu) lstat ("plik1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat („file2”, 0x7ffe3e705a10) = -1 ENOENT (Brak takiego pliku lub katalogu) zmiana nazwy („file1”, „file2”) = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (nielegalne poszukiwanie). Wygląda na to, że zmieniono nazwę. @ Rozwiązanie StéphaneChazelas wydaje się być odpowiednie, jeśli naprawdę chcesz zrobić to bez wyścigu.
Fabian Schmitthenner,
2
Zastanawiam się, dlaczego się nie stosujerenameat2
Fabian Schmitthenner,
16

mv -n

Z man mvsystemu GNU:

-n, --no-clobber
nie zastępuje istniejącego pliku

W systemie FreeBSD:

-nNie zastępuj istniejącego pliku. (Opcja -n zastępuje wszelkie wcześniejsze opcje -f lub -i).

Dani_l
źródło
10
if [ ! -e file2 ] && [ ! -L file2 ]
then
    mv file1 file2
# else echo >&2 there is already a file2 file.
fi

Lub:

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

Działa tylko, mvjeśli file2nie istnieje. Zauważ, że nie gwarantuje to, że a file2nie zostanie zastąpione, ponieważ file2można było utworzyć między testem a testem mv, ale zauważ, że przynajmniej obecne wersje GNU mvz taką gwarancją -ilub -nnie dają takiej gwarancji (chociaż warunki wyścigu są węższe tam, ponieważ kontrola odbywa się w ciągu mv).

Z drugiej strony jest przenośny, pozwala rozróżniać przypadki i działa niezależnie od typu file2pliku (zwykły, potokowy, a nawet katalog ).

Majenko
źródło
3
czy wprowadza to warunek wyścigu, w którym można zapisać plik między sprawdzeniem istnienia a przeprowadzką?
Fabian Schmitthenner,
3
Zawsze istnieje możliwość, cokolwiek robisz.
Majenko,
3
Linux API ma, renameat2co możesz dać RENAME_NOREPLACEflagę. Wierzę, że to atomowo sprawdza istnienie pliku, a następnie przenosi plik.
Fabian Schmitthenner,
-d dla katalogów lub -l dla łączy, a nawet -e dla dowolnego typu pliku
Majenko
zmiana nazwy może być wolna od wyścigu, ale reszta polecenia mv nie. Jeśli myśli, że nie musi się rozłączać, to nagle zmiana nazwy kończy się niepowodzeniem, to (powinien) błąd.
Majenko,
8

Podejście bez wyścigu z lnudostępnionym GNU file1nie jest katalogiem typu :

ln -PT file1 file2 && rm file1

(Z wyjątkiem błędów w niektórych sieciowych systemach plików), co gwarantuje, że żaden file2plik nie zostanie przesłonięty (lub, że jeśli file2katalog typu file1jest nie przeniesiony do niego), ponieważ link()wywołanie systemowe, w przeciwieństwie do rename()wywołania systemowego, zakończy się niepowodzeniem, jeśli cel istnieje.

Będzie jednak stan pośredni, w którym plik istnieje zarówno jako, jak file1i file2.

-TOpcja (zawsze zrobić link("file1", "file2"), nawet jeśli file2jest z katalogu typu) jest specyficzne dla GNU.

Możesz także użyć linkpolecenia:

link file1 file2 && rm file1

Jeśli jednak file1jest dowiązaniem symbolicznym, w zależności od implementacji file2będzie dowiązaniem twardym do tego dowiązania symbolicznego lub do celu tego dowiązania symbolicznego (w Solarisie, użyj /usr/sbin/link, nie /usr/xpg4/bin/link).

Stéphane Chazelas
źródło
2
wiesz, czy linux api renameat2z flagą RENAME_NOREPLACEjest atomowy?
Fabian Schmitthenner,
1
@Fabian, AFAICT jest przeznaczony, ale jest bardzo nowy i nie jest obsługiwany dla wszystkich systemów plików. Idąc dalej, możemy spodziewać się, że w przyszłości zastosują to mv w Linuksie. Do tego został zaprojektowany.
Stéphane Chazelas,
0

Możesz także użyć parametru, test -e namektóry zwróci true, jeśli nazwa istnieje (niezależnie od pliku, katalogu lub dowiązania symbolicznego).

Na przykład:

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
źródło
1
Ale ln -s doesnotexist exists; test -e exists || echo "does it really not exist?"… To samo na przykład ln -s /var/spool/cron/crontabs/. exists(i nie jesteś rootem ani członkiem grupy crontab).
Stéphane Chazelas,