mv: Перемістити файл лише в тому випадку, якщо призначення не існує


44

Можу чи я використовувати mv file1 file2таким чином , що вона рухається тільки file1в file2разі file2не існує?

Я намагався

yes n | mv -i file1 file2

(це дозволяє mvзапитати, чи слід file2 переосмислити і автоматично відповісти "ні"), але, окрім зловживань, -iвін також не дає мені гарних кодів помилок (завжди 141 замість 0, якщо переміщено, і щось інше, якщо не переміщено)


3
Ви повинні мати pipefailопцію, оскільки 141 буде статусом виходу yes, а mvце не матиме жодних підстав для отримання ЗНАЧЕННЯ тут.
Стефан Шазелас

Цей підхід також не вдається, якщо file2 - це каталог (він перемістить файл1 у каталог file2). GNU mv має -Tдля цього.
Стефан Шазелас

@ StéphaneChazelas Якщо бажання використовувати статус виходу, mvа не той yes, найпростішим рішенням може бутиmv -i file1 file2 < <(yes n)
kasperd

Відповіді:


63

mv -vn file1 file2. Ця команда буде робити те, що ви хочете. Ви можете пропустити, -vякщо хочете.

-v робить його багатослівним - mv скаже вам, що він перемістив файл, якщо він перемістить його (корисно, оскільки є можливість, що файл не буде переміщений)

-n переміщується, лише якщо file2 не існує.

Зауважте, однак, що це не POSIX, як згадував ThomasDickey .


2
Однак це не POSIX .
Томас Дікі

1
@ThomasDickey чи POSIX взагалі підтримує це атомним шляхом?
Фабіан Шміттеннер

3
до @Fabian: Напевно, ні, але навіть у запропонованих відповідях існує можливість гонки в межах інструментів, залежно від того, як вони написані.
Томас Дікі

3
це, здається, не є гоночним, straceпоказує, що він використовує (в моїй системі): stat ("file2", 0x7ffe3e705d10) = -1 ENOENT (немає такого файлу чи каталогу) lstat ("file1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("file2", 0x7ffe3e705a10) = -1 ENOENT (немає такого файлу чи каталогу) перейменувати ("file1", "file2") = 0 lseek (0, 0, SEEK_CUR) = -1 ESPIPE (Незаконний пошук). Тому перейменування, здається, використовується. @ Рішення StéphaneChazelas, здається, є правильним, якщо ви дійсно хочете зробити це гонки безкоштовно.
Фабіан Шміттенер

2
Цікаво, чому це не використовуєтьсяrenameat2
Фабіан Шміттеннер

16

mv -n

З man mvсистеми GNU:

-n, --no-clobber
не перезаписує існуючий файл

У системі FreeBSD:

-nНе перезаписувати наявний файл. (Параметр -n перекриває будь-які попередні параметри -f або -i.)


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

Або:

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

Запускається, лише mvякщо file2не існує. Зверніть увагу , що це не гарантує , що file2НЕ буде перевизначений , так як file2могли бути створені між тестом і mv, але врахуйте , що принаймні версій поточних ГНУ mvз -iабо -nне дають гарантії , що або (хоча стан гонки є більш вузьким там, оскільки перевірка робиться всередині mv).

З іншого боку, він портативний, дозволяє розрізняти регістри та працює незалежно від типу file2файлу (звичайний, трубовий, навіть каталог ).


3
чи вводиться це умова перегонів, коли може бути записаний файл між перевіркою існування та переміщенням?
Фабіан Шміттеннер

3
Завжди можливість все, що ти робиш.
Маєнко

3
Linux API має, на renameat2якому можна надати RENAME_NOREPLACEпрапор. Я вважаю, що це атомно перевіряє існування файлу, а потім переміщує файл.
Фабіан Шміттеннер

-d для каталогів, або -l для посилань або навіть -e для будь-якого типу файлів
Majenko

перейменування може бути без гонки, але решта команди mv - ні. Якщо він вважає, що не потрібно відключати, то раптом перейменування не вдасться, воно (помилка).
Маєнко

8

Безгоновий підхід із lnнаданим GNU file1не є типовим каталогом :

ln -PT file1 file2 && rm file1

(За винятком помилок у деяких мережевих файлових системах), це гарантує, що жоден file2файл не буде переосмислений (або що, якщо file2це каталог типу, file1не буде переміщений в нього), оскільки link()системний виклик, всупереч rename()системному виклику, не вдасться, якщо ціль існує.

Однак буде проміжний стан, коли файл існує і як, file1і file2.

-TВаріант (завжди робити link("file1", "file2")навіть якщо file2є директорії типу) є GNU-специфічно.

Ви також можете скористатися linkкомандою:

link file1 file2 && rm file1

Однак, якщо file1це симпосилання, залежно від реалізації, воно file2буде або жорстким посиланням на це символьне посилання, або до цілі цього символьного посилання (для Solaris, використання /usr/sbin/link, ні /usr/xpg4/bin/link).


2
чи знаєте ви, чи Linux api renameat2з прапором RENAME_NOREPLACEє атомним?
Фабіан Шміттеннер

1
@Fabian, AFAICT призначений для цього, але він дуже новий і підтримується не для всіх файлових систем. Вперед ми можемо очікувати, що майбутні впровадження mv в Linux будуть використовувати це. Саме для цього він і був розроблений.
Стефан Шазелас

0

Ви також можете використовувати, test -e nameщо поверне значення true, якщо ім'я існує (незалежно від файлу, каталогу чи символьної посилання).

Наприклад:

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
Але ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Так само, наприклад, ln -s /var/spool/cron/crontabs/. exists(і ви не є кореневим або членом групи crontab).
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.