mv: перемещать файл, только если место назначения не существует


44

Могу ли я использовать mv file1 file2таким образом , что она движется только file1в file2случае file2не существует?

я пробовал

yes n | mv -i file1 file2

(это позволяет mvспросить, должен ли файл2 быть переопределен и автоматически ответить «нет»), но кроме злоупотребления -iон также не дает мне хороших кодов ошибок (всегда 141 вместо 0, если перемещено, и что-то еще, если не перемещено)


3
У вас должна быть pipefailопция, так как 141 будет статусом выхода yes, но это не mvбудет иметь никаких причин для получения SIGPIPE здесь.
— Стефан Шазелас

Этот подход также терпит неудачу, если file2 является каталогом (он переместит file1 в каталог 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
to @Fabian: Вероятно, нет, но даже в рамках предложенных ответов есть вероятность гонки в инструментах, в зависимости от того, как они написаны.
— Томас Дики

3
это, кажется, не является свободным от гонки, straceпоказывает, что он использует (в моей системе): stat ("file2", 0x7ffe3e705d10) = -1 ENOENT (нет такого файла или каталога) lstat ("file1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("file2", 0x7ffe3e705a10) = -1 ENOENT (такой файл или каталог отсутствует) rename ("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не существует. Обратите внимание, что это не гарантирует, что a file2не будет переопределено, потому file2что между тестом и функцией могло быть создано a mv, но учтите, что по крайней мере текущие версии GNU 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
Вы знаете, если API Linux 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(и вы не являетесь пользователем root или членом группы crontab).
— Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.