mv: Chỉ di chuyển tệp nếu không có đích


44

Tôi có thể sử dụng mv file1 file2theo cách mà nó chỉ di chuyển file1đến file2nếu file2không tồn tại?

Tôi đã thử

yes n | mv -i file1 file2

(điều này cho phép mvhỏi liệu file2 có bị ghi đè và tự động trả lời không) nhưng bên cạnh việc lạm dụng -inó cũng không cung cấp cho tôi mã lỗi đẹp (luôn luôn thay vì 0 nếu di chuyển và một cái gì khác nếu không di chuyển)


3
Bạn phải có pipefailtùy chọn trên 141 sẽ là trạng thái thoát yes, không mvcó lý do gì để có SIGPIPE ở đây.
Stéphane Chazelas

Cách tiếp cận đó cũng thất bại nếu file2 là một thư mục (nó sẽ di chuyển file1 vào thư mục file2). GNU mv có một -Tcho điều đó.
Stéphane Chazelas

@ StéphaneChazelas Nếu mong muốn là sử dụng trạng thái thoát mvthay vì yes, thì giải pháp đơn giản nhất có thể làmv -i file1 file2 < <(yes n)
kasperd

Câu trả lời:


63

mv -vn file1 file2. Lệnh này sẽ làm những gì bạn muốn. Bạn có thể bỏ qua -vnếu bạn muốn.

-v làm cho nó dài dòng - mv sẽ cho bạn biết rằng nó đã di chuyển tệp nếu nó di chuyển nó (hữu ích, vì có khả năng tệp sẽ không được di chuyển)

-n chỉ di chuyển nếu file2 không tồn tại.

Tuy nhiên, xin lưu ý rằng đây không phải là POSIX như ThomasDickey đã đề cập .


2
Tuy nhiên, đó không phải là POSIX .
Thomas Dickey

1
@ThomasDickey POSIX có hỗ trợ điều này theo cách nguyên tử không?
Fabian Schmitthenner

3
tới @Fabian: Có thể là không, nhưng ngay cả trong các câu trả lời được đề xuất, vẫn có khả năng có một cuộc đua trong các công cụ, tùy thuộc vào cách chúng được viết.
Thomas Dickey

3
điều này dường như không phải là cuộc đua miễn phí, stracecho thấy rằng nó sử dụng (trên hệ thống của tôi): stat ("file2", 0x7ffe3e705d10) = -1 ENOENT (Không có tệp hoặc thư mục như vậy) lstat ("file1", {st_mode = S_IFREG | 0644, st_size = 0, ...}) = 0 lstat ("file2", 0x7ffe3e705a10) = -1 ENOENT (Không có tệp hoặc thư mục như vậy) đổi tên ("file1", "file2") = 0 lseek (0, 0, XEMK_CUR) = -1 TRÒ CHƠI (Tìm kiếm bất hợp pháp). Vì vậy, đổi tên dường như được sử dụng. Giải pháp @ StéphaneChazelas dường như là giải pháp phù hợp nếu bạn thực sự muốn thực hiện nó miễn phí.
Fabian Schmitthenner

2
Tôi tự hỏi tại sao nó không sử dụngrenameat2
Fabian Schmitthenner

16

mv -n

Từ man mvtrên hệ thống GNU:

-n, --no-clobber
không ghi đè lên tệp hiện có

Trên hệ thống FreeBSD:

-nKhông ghi đè lên một tập tin hiện có. (Tùy chọn -n ghi đè mọi tùy chọn -f hoặc -i trước đó.)


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

Hoặc là:

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

Sẽ chỉ chạy mvnếu file2không tồn tại. Lưu ý rằng điều đó không đảm bảo rằng file2sẽ không bị ghi đè vì file2có thể đã được tạo giữa thử nghiệm và thử nghiệm mv, nhưng lưu ý rằng ít nhất các phiên bản hiện tại của GNU mv-ihoặc -nkhông đảm bảo điều đó (mặc dù điều kiện cuộc đua hẹp hơn có kể từ khi kiểm tra được thực hiện trong mv).

Mặt khác, nó là thiết bị di động, cho phép bạn phân biệt giữa các trường hợp và hoạt động bất kể loại file2tệp (thông thường, đường ống, thậm chí thư mục ).


3
điều này có đưa ra một điều kiện cuộc đua trong đó một tập tin có thể được viết giữa kiểm tra tồn tại và di chuyển không?
Fabian Schmitthenner

3
Luôn luôn là một khả năng bất cứ điều gì bạn làm.
Majenko

3
API Linux có renameat2mà bạn có thể đưa ra một RENAME_NOREPLACEcờ. Tôi tin rằng nguyên tử này kiểm tra sự tồn tại của tập tin và sau đó di chuyển tập tin.
Fabian Schmitthenner

-d cho các thư mục hoặc -l cho các liên kết hoặc thậm chí -e cho bất kỳ loại tệp nào
Majenko

việc đổi tên có thể là cuộc đua miễn phí nhưng phần còn lại của lệnh mv thì không. Nếu nó nghĩ rằng nó không cần phải hủy liên kết thì đột nhiên việc đổi tên không thành công thì nó sẽ (lỗi).
Majenko

8

Cách tiếp cận không có chủng tộc với GNU lnđược cung cấp file1không thuộc loại thư mục :

ln -PT file1 file2 && rm file1

(Ngoại trừ lỗi trong một số hệ thống tệp mạng), đảm bảo rằng không có file2tệp nào bị ghi đè (hoặc nếu file2thuộc loại thư mục, file1sẽ không được chuyển vào đó), vì link()cuộc gọi hệ thống, trái với lệnh rename()gọi hệ thống sẽ thất bại nếu mục tiêu tồn tại.

Tuy nhiên, sẽ có một trạng thái trung gian nơi tệp tồn tại cả dưới dạng file1file2.

Các -Ttùy chọn (để luôn luôn làm một link("file1", "file2")ngay cả khi file2là loại thư mục) là GNU cụ thể.

Bạn cũng có thể sử dụng linklệnh:

link file1 file2 && rm file1

Tuy nhiên, nếu file1là một liên kết tượng trưng, tùy thuộc vào việc thực hiện, file2sẽ là một trong hai một hardlink để liên kết tượng trưng đó hoặc với mục tiêu của liên kết tượng trưng đó (trên Solaris, sử dụng /usr/sbin/link, không /usr/xpg4/bin/link).


2
Bạn có biết api linux renameat2với cờ RENAME_NOREPLACElà nguyên tử không?
Fabian Schmitthenner

1
@Fabian, AFAICT có nghĩa là nhưng nó rất mới và không được hỗ trợ cho tất cả các hệ thống tập tin. Trong tương lai, chúng ta có thể mong đợi các triển khai mv trong tương lai trên Linux sẽ sử dụng điều đó. Đó là những gì nó được thiết kế cho.
Stéphane Chazelas

0

Bạn cũng có thể sử dụng test -e namesẽ trả về true nếu tên tồn tại (bất kể tệp, thư mục hoặc symlink).

Ví dụ:

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
Nhưng ln -s doesnotexist exists; test -e exists || echo "does it really not exist?". Tương tự với ví dụ ln -s /var/spool/cron/crontabs/. exists(và bạn không phải là root hoặc thành viên của nhóm crontab).
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.