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)


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 .


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).


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 ).


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).


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...

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,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.