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 .

MatthewRock
источник
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.)

Dani_l
источник
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файла (обычный, конвейерный, даже каталог ).

Majenko
источник
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...
H Briceno
источник
1
Но ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". То же самое, например, ln -s /var/spool/cron/crontabs/. exists(и вы не являетесь пользователем root или членом группы crontab).
Стефан Шазелас