Cách đổi tên nhiều tệp bằng find


37

Tôi muốn đổi tên nhiều tệp (file1 ... filen thành file1_renamed ... filen_renamed) bằng lệnh findlệnh:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Nhưng nhận được lỗi này:

mv: cannot stat filename=./file1’: No such file or directory

Điều này không hoạt động vì tên tệp không được hiểu là biến shell.

Câu trả lời:


46

Sau đây là cách khắc phục trực tiếp phương pháp của bạn:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Tuy nhiên, điều này rất tốn kém nếu bạn có nhiều tệp phù hợp, bởi vì bạn bắt đầu một lớp vỏ mới (thực thi a mv) cho mỗi trận đấu. Và nếu bạn có các ký tự vui nhộn trong bất kỳ tên tệp nào, nó sẽ phát nổ. Một cách tiếp cận hiệu quả và an toàn hơn là:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Nó cũng có lợi ích khi làm việc với các tệp có tên lạ. Nếu findhỗ trợ nó, điều này có thể được giảm xuống

find . -type f -name 'file*' -exec mv {} {}_renamed \;

Các xargsphiên bản rất hữu ích khi không sử dụng {}, như trong

find .... -print0 | xargs --null rm

Ở đây rmđược gọi một lần (hoặc với nhiều tệp nhiều lần), nhưng không phải cho mọi tệp.

Tôi đã xóa basenamecâu hỏi trong bạn, bởi vì nó có thể sai: bạn sẽ chuyển foo/bar/file8đến file8_renamed, không foo/bar/file8_renamed.

Chỉnh sửa (như được đề xuất trong ý kiến):

  • Đã thêm rút ngắn findmà không cầnxargs
  • Đã thêm nhãn dán bảo mật

Trong trường hợp xlà vô dụng: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsphiên bản có hiệu quả tương tự như ví dụ đầu tiên /
Costas

Chỉ xcó để sửa chữa trực tiếp phương pháp của người hỏi.
Thomas Erker

2
(1) Sẽ rất nguy hiểm khi sử dụng {}trực tiếp trong sh -c "…"lệnh shell ( ) - bạn phải luôn truyền nó dưới dạng đối số. (2) Không phải tất cả các phiên bản findhỗ trợ {}_renamedxây dựng. (3) Tôi không hiểu tuyên bố của bạn xargshữu ích cho việc xóa các tệp (ngược lại với việc đổi tên chúng).
G-Man nói 'Phục hồi Monica'

@ G-Man: Sự khác biệt với xargskhông phải là mvso với rm, nhưng sử dụng {}so với không có. Cái trước tương tự như mv file1 file1_renamed; mv file2 file2_renamedcái sau rm file1 file2.
Thomas Erker

1
và nếu sau đó bạn muốn thay đổi các tệp _renamed trở lại tên gốc của chúng, bạn sẽ làm thế nào?
mcmillab

17

Sau khi thử câu trả lời đầu tiên và chơi đùa với nó một chút, tôi thấy rằng nó có thể được thực hiện ngắn hơn một chút và ít phức tạp hơn bằng cách sử dụng -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Có vẻ như nó cũng nên làm chính xác những gì bạn cần.


2
Với các findtriển khai hỗ trợ -execdir{}không nói chung, đó cũng là cách an toàn nhất. Bạn có thể muốn thêm một -iđể mvdù (và -Tnếu bạn mvhỗ trợ nó)
Stéphane Chazelas

1
@ StéphaneChazelas thậm chí tốt hơn, thay vì dựa vào mvmột dấu nhắc, hoặc bổ sung cho nó, bạn có thể (không nghi ngờ gì nữa tùy thuộc vào việc thực hiện của bạn findhỗ trợ nó) cũng sử dụng -okdirmà sẽ ra lệnh được thực thi trước khi thực hiện nó.
Matijs

Đáng nói đó -depthcũng là một ý tưởng tốt nếu bạn cũng sẽ chạm vào tên thư mục.
Ciro Santilli 改造 心 心

Argh, chỉ cần phát hiện ra rằng -execdirkhông có một nhược điểm rất khó chịu, findtừ chối làm bất cứ điều gì nếu PATHcó bất kỳ đường dẫn tương đối ... askubuntu.com/questions/621132/... find: The relative path XXX is included in the PATH environment
Ciro Santilli新疆改造中心法轮功六四事件

6

Một cách tiếp cận khác là sử dụng một while readvòng lặp trên findđầu ra. Điều này cho phép truy cập vào mỗi tên tập tin như là một biến có thể được thao tác mà không cần phải lo lắng về vấn đề an ninh chi phí / tiềm năng bổ sung của sinh sản một riêng biệt sh -cquá trình sử dụng findcủa -exectùy chọn.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

Và nếu shell đang được sử dụng hỗ trợ -dtùy chọn chỉ định một readdấu phân cách, bạn có thể hỗ trợ các tệp có tên lạ (ví dụ: với một dòng mới) bằng cách sử dụng như sau:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

3

Tôi muốn mở rộng câu trả lời đầu tiên và lưu ý rằng điều này sẽ không hoạt động để nối vào tên tệp vì ./tiền tố đường dẫn có trong đối số tên tệp.

Sửa đổi câu trả lời của Thomas Erker, tôi thấy đây là một cách tiếp cận chung chung hơn

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

tùy chọn xargs:

--nullChỉ ra rằng mỗi đối số được truyền qua stdinkết thúc bằng ký tự null ( \0). Bằng cách này, tên tệp có thể chứa khoảng trắng, nếu không, mỗi từ sẽ bị đe dọa như là một tham số khác nhau cho mvlệnh.

-I replace-strMọi thông số replace-strsẽ được thay thế bằng đối số được đọc từ stdin. Vì vậy, bạn có thể thay đổi nó cho chuỗi khác nếu bạn cần nó.


Tại sao làm pringf "%f\0"thay vì một print0?
slm

1
@sim, bởi vì -print0sẽ tạo ./tiền tố nếu PATTERNchứa các siêu ký tự shell gây cản trở khi đổi tên thành thứ gì đó có tiền tố tên gốc. (ví dụ như đổi tên 0 - foo.txtđể 00 - foo.txt, 1 - bar.txtđể 01 - bar.txt, vv)
das-g

2

Tôi đã có thể làm điều gì đó tương tự với for, find, và mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Điều này tìm thấy tất cả các config.ymltập tin và đổi tên chúng thànhconfig.yml.bak


điều này có lợi thế là có thể sử dụng mở rộng biến, ví dụ. $ {i: r}. Câu trả lời tốt đẹp.
Salami

Vâng, bạn có thể đặt bất kỳ biểu thức nào vào forvà thực hiện một thao tác hàng loạt.
Varun Risbud
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.