Đổi tên trả lại thanh bareword không được phép khi cố gắng viết thường các phần của nhiều tên tệp


12

Tôi có hai tệp trong một thư mục trên Ubuntu 16.04:

a1.dat
b1.DAT

Tôi muốn đổi tên b1.DATthành b1.datvì vậy tôi sẽ có các tệp sau đây trong thư mục:

a1.dat
b1.dat

Tôi đã thử (không thành công):

$ rename *.DAT *.dat
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

$ find . -iname "*.DAT" -exec rename DAT dat '{}' \;
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Tìm kiếm điều này dẫn đến không có giải pháp có ý nghĩa ...


Bạn cũng có thể muốn tìm hiểu về mmv
PlasmaHH 17/03/18

Câu trả lời:


14

Lỗi đó có vẻ như đến từ Perl rename. Bạn cần sử dụng trích dẫn, nhưng bạn chỉ cần xác định phần bạn muốn thay đổi, kiểu tìm kiếm và thay thế. Cú pháp như thế này:

rename -n 's/\.DAT/\.dat/' *

Loại bỏ -nsau khi thử nghiệm để thực sự đổi tên các tập tin.

Để bao gồm các tệp ẩn, thay đổi cài đặt toàn cầu trước khi chạy lệnh:

shopt -s dotglob

Nếu bạn muốn đổi tên các tệp đệ quy, bạn có thể sử dụng

shopt -s globstar
rename -n 's/\.DAT/\.dat/' **

hoặc nếu có nhiều đường dẫn trong hoặc bên dưới thư mục hiện tại không kết thúc .DAT, bạn nên xác định rõ hơn các đường dẫn đó trong lệnh thứ hai:

rename -n 's/\.DAT/\.dat/' **/*.DAT

Điều này sẽ nhanh hơn nếu các tệp của bạn có nhiều tên khác nhau không kết thúc .DAT. [1]

Để tắt các cài đặt này, bạn có thể sử dụng shopt -u, ví dụ shopt -u globstar, nhưng chúng bị tắt theo mặc định và sẽ tắt khi bạn mở một vỏ mới.

Nếu điều này dẫn đến một danh sách đối số quá dài, bạn có thể sử dụng, ví dụ find:

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} \;

hoặc tốt hơn

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} +

Sử dụng find ... -execvới +nhanh hơn sử dụng \;vì nó xây dựng một danh sách đối số từ các tệp tìm thấy. Ban đầu tôi nghĩ rằng bạn sẽ không thể sử dụng nó, bởi vì bạn đã đề cập rằng bạn đang gặp argument list too longvấn đề, nhưng bây giờ tôi biết rằng danh sách này cũng sẽ được chia thành nhiều lệnh một cách khéo léo khi cần để tránh vấn đề đó [2] .

renamesẽ xử lý mọi tên tệp theo cùng một cách, không quan trọng danh sách đối số dài bao nhiêu vì nó có thể được phân chia an toàn qua nhiều lần gọi. Nếu lệnh bạn đang sử dụng -execkhông chấp nhận nhiều đối số hoặc yêu cầu các đối số của nó phải theo một thứ tự cụ thể hoặc vì bất kỳ lý do nào khác, việc tách danh sách đối số sẽ khiến điều gì đó không mong muốn xảy ra, bạn có thể sử dụng \;, khiến lệnh được gọi một lần cho mỗi tệp được tìm thấy (nếu danh sách đối số quá dài đối với các phương thức khác, việc này sẽ mất nhiều thời gian!).


Rất cám ơn Eliah Kagan vì những gợi ý rất hữu ích để cải thiện câu trả lời này:

[1] Chỉ định tên tệp khi toàn cầu hóa .
[2] find ... -execvới việc +tách danh sách đối số .


Cảm ơn. Làm thế nào để làm điều đó đệ quy (cho tất cả các tệp trong tất cả các thư mục con)?
Samo

@Samo xem bản chỉnh sửa của tôi :)
Zanna

Không hoạt động: $ đổi tên 's / \. DAT / \. Dat /' ** bash: / usr / bin / rename: Danh sách đối số quá dài
Samo

1
@Samo bạn cần xóa -ntên khỏi đổi tên sau khi thử nghiệm - chỉ để hiển thị những gì sẽ được thay đổi
Zanna

1
Trên Centos và Arch, đổi tên không thể hiện hành vi này. Tôi đã đến đây từ việc sử dụng Debian trên WSL. Cú pháp này đã làm việc.
xtian

6

Bạn có thể làm:

rename -n 's/DAT$/\L$&/' *.DAT

Thả -nđể đổi tên thực tế diễn ra.

  • mẫu *.DATtoàn cục khớp với tất cả các tệp kết thúc trong .DATthư mục hiện tại

  • trong sự renamethay thế của DAT$trận đấu , trận đấu DATở cuối

  • \L$&làm cho toàn bộ trận đấu hạ thấp; $&đề cập đến toàn bộ trận đấu

Nếu bạn chỉ muốn làm cho b1.DAT:

rename -n 's/DAT$/\L$&/' b1.DAT

Thí dụ:

% rename -n 's/DAT$/\L$&/' *.DAT
rename(b1.DAT, b1.dat)

4

Các câu trả lời khác đã giải quyết một trong hai khía cạnh chính của câu hỏi này: đó là làm thế nào để thực hiện thành công thao tác đổi tên mà bạn cần. Mục đích của câu trả lời này là để giải thích lý do tại sao các lệnh của bạn không hoạt động, bao gồm cả ý nghĩa của thông báo lỗi "bareword không được phép" kỳ lạ đó trong ngữ cảnh của renamelệnh.

Phần đầu tiên của câu trả lời này là về mối quan hệ giữa renamevà Perl và cách renamesử dụng đối số dòng lệnh đầu tiên bạn truyền cho nó, đó là đối số mã của nó. Phần thứ hai là về cách trình bao thực hiện các mở rộng - cụ thể là toàn cầu hóa - để xây dựng một danh sách đối số. Phần thứ ba là về những gì đang diễn ra trong mã Perl có lỗi "Bareword không được phép". Cuối cùng, phần thứ tư là một bản tóm tắt của tất cả các bước diễn ra giữa việc nhập lệnh và nhận lỗi.

1. Khi renamecung cấp cho bạn thông báo lỗi lạ, hãy thêm "Perl" vào tìm kiếm của bạn.

Trong Debian và Ubuntu, renamelệnh là tập lệnh Perl thực hiện đổi tên tệp. Trên các bản phát hành cũ hơn - bao gồm 14.04 LTS, vẫn được hỗ trợ khi viết bài này - đó là một liên kết tượng trưng chỉ ( gián tiếp ) đến prenamelệnh. Trên các bản phát hành mới hơn, nó thay vào đó là file-renamelệnh mới hơn . Những hai lệnh đổi tên Perl làm việc chủ yếu là theo cùng một cách, và tôi sẽ chỉ đề cập đến cả hai như renamecho phần còn lại của câu trả lời này.

Khi bạn sử dụng renamelệnh, bạn không chỉ chạy mã Perl mà người khác đã viết. Bạn cũng đang viết mã Perl của riêng bạn và bảo renamechạy nó. Điều này là do đối số dòng lệnh đầu tiên bạn truyền cho lệnh, ngoài các đối số tùy chọn như , bao gồm mã Perl thực tếrename-n . Các renamelệnh sử dụng mã này để hoạt động trên mỗi tên đường dẫn mà bạn vượt qua nó như các đối số dòng lệnh tiếp theo. (Nếu bạn không vượt qua bất kỳ đối số tên đường dẫn nào, thì hãy renameđọc tên đường dẫn từ đầu vào tiêu chuẩn , thay vào đó là một đối số trên mỗi dòng.)

Mã được chạy bên trong một vòng lặp , lặp lại một lần cho mỗi tên đường dẫn. Ở đầu mỗi lần lặp của vòng lặp, trước khi mã của bạn chạy, $_biến đặc biệt được gán tên đường dẫn hiện đang được xử lý. Nếu mã của bạn khiến giá trị của $_bị thay đổi thành một thứ khác, thì tệp đó được đổi tên thành tên mới.

Nhiều biểu thức trong Perl hoạt động ngầm định trên $_biến khi chúng không được cung cấp bất kỳ biểu thức nào khác để sử dụng làm toán hạng . Ví dụ, biểu thức thay thế thay $str =~ s/foo/bar/đổi lần xuất hiện đầu tiên của foochuỗi được giữ bởi $str biến thành barhoặc không thay đổi nếu nó không chứa foo. Nếu bạn chỉ cần viết s/foo/bar/mà không sử dụng một cách rõ ràng các =~nhà điều hành , sau đó nó hoạt động trên $_. Điều này để nói rằng đó s/foo/bar/là viết tắt của $_ =~ s/foo/bar/.

Việc chuyển một s///biểu thức thành renameđối số mã (nghĩa là đối số dòng lệnh đầu tiên) là điều phổ biến, nhưng bạn không phải làm vậy. Bạn có thể cung cấp cho nó bất kỳ mã Perl nào bạn muốn cho nó chạy bên trong vòng lặp, để kiểm tra từng giá trị $_và (điều kiện) sửa đổi nó.

Điều này có rất nhiều hậu quả thú vị và hữu ích , nhưng phần lớn trong số chúng vượt xa phạm vi của câu hỏi và câu trả lời này. Lý do chính tôi đưa vấn đề này lên đây - thực tế, lý do chính khiến tôi quyết định đăng câu trả lời này - là để đưa ra quan điểm rằng, bởi vì đối số đầu tiên renamethực sự là mã Perl, bất cứ khi nào bạn nhận được thông báo lỗi lạ và bạn gặp khó khăn khi tìm thông tin về nó bằng cách tìm kiếm, đôi khi bạn có thể thêm "Perl" vào chuỗi tìm kiếm (hoặc thậm chí thay thế "đổi tên" bằng "Perl" và đôi khi bạn sẽ tìm thấy câu trả lời.

2. Với rename *.DAT *.dat, renamelệnh không bao giờ thấy *.DAT!

Một lệnh như rename s/foo/bar/ *.txtthường không chuyển *.txtnhư một đối số dòng lệnh cho renamechương trình và bạn không muốn nó , trừ khi bạn có một tệp có tên theo nghĩa đen *.txt, hy vọng bạn không làm như vậy.

renamekhông giải thích mẫu glob thích *.txt, *.DAT, *.dat, x*, *y, hoặc *khi truyền cho nó như các đối số tên đường dẫn. Thay vào đó, shell của bạn thực hiện mở rộng tên đường dẫn trên chúng (còn được gọi là mở rộng tên tệp và còn được gọi là globalbing). Điều này xảy ra trước khi renametiện ích được chạy. Shell mở rộng các khối u thành nhiều tên đường tiềm năng và chuyển tất cả chúng, dưới dạng các đối số dòng lệnh riêng biệt, sang rename. Trong Ubuntu, vỏ tương tác của bạn là Bash , trừ khi bạn đã thay đổi nó, đó là lý do tại sao tôi đã liên kết với hướng dẫn tham khảo Bash ở trên.

Có một tình huống trong đó một mẫu hình cầu có thể được chuyển qua dưới dạng một đối số dòng lệnh chưa được mở rộng thành rename: khi nó không khớp với bất kỳ tệp nào. Các shell khác nhau thể hiện hành vi mặc định khác nhau trong tình huống này, nhưng hành vi mặc định của Bash chỉ đơn giản là vượt qua toàn cầu theo nghĩa đen. Tuy nhiên, bạn hiếm khi muốn điều này! Nếu bạn đã muốn nó, thì bạn nên đảm bảo rằng mô hình không được mở rộng, bằng cách trích dẫn nó. Điều này đi để truyền đối số cho bất kỳ lệnh nào, không chỉ để rename.

Trích dẫn không chỉ dành cho toàn cầu hóa (mở rộng tên tệp), bởi vì có những mở rộng khác mà trình bao của bạn thực hiện trên văn bản không được trích dẫn và, đối với một số trong số chúng nhưng không phải trên văn bản được đặt trong " "dấu ngoặc kép. Nói chung, bất cứ khi nào bạn muốn vượt qua một đối số có chứa các ký tự có thể được xử lý đặc biệt bởi trình bao, bao gồm cả khoảng trắng, bạn nên trích dẫn nó, tốt nhất là bằng ' 'dấu ngoặc kép .

Mã Perl s/foo/bar/không chứa bất cứ thứ gì được xử lý đặc biệt bởi shell, nhưng nó cũng là một ý tưởng tốt cho tôi để trích dẫn điều đó - và được viết 's/foo/bar/'. (Trên thực tế, lý do duy nhất tôi không làm là nó gây nhầm lẫn cho một số độc giả, vì tôi chưa nói về việc trích dẫn.) Lý do tôi nói điều này sẽ tốt là vì rất phổ biến rằng mã Perl chứa những ký tự như vậy và nếu tôi thay đổi mã đó, tôi có thể không nhớ kiểm tra xem có cần trích dẫn không. Ngược lại, nếu bạn muốn shell mở rộng toàn cầu, nó không được trích dẫn.

3. Trình thông dịch Perl nghĩa là gì bởi "bareword không được phép"

Các thông báo lỗi bạn hiển thị trong câu hỏi của bạn tiết lộ rằng, khi bạn chạy rename *.DAT *.dat, trình bao của bạn mở rộng *.DATthành một danh sách một hoặc nhiều tên tệp và rằng tên tệp đầu tiên trong số đó là b1.DAT. Tất cả các đối số tiếp theo - cả bất kỳ đối số nào khác được mở rộng từ *.DATvà bất kỳ đối số nào được mở rộng từ *.dat--came sau đối số đó, vì vậy chúng sẽ được hiểu là tên đường dẫn.

Bởi vì những gì thực sự chạy là một cái gì đó giống như rename b1.DAT ...và vì renamecoi đối số không phải là tùy chọn đầu tiên của nó là mã Perl, câu hỏi trở thành: tại sao lại b1.DATtạo ra các lỗi "bareword không được phép" này khi bạn chạy nó dưới dạng mã Perl?

Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Trong một shell, chúng tôi trích dẫn các chuỗi của chúng tôi để bảo vệ chúng khỏi các mở rộng shell ngoài ý muốn , nếu không chúng sẽ tự động chuyển chúng thành các chuỗi khác (xem phần bên trên). Shell là ngôn ngữ lập trình mục đích đặc biệt hoạt động rất khác với ngôn ngữ có mục đích chung (và cú pháp và ngữ nghĩa rất kỳ lạ của chúng phản ánh điều đó). Nhưng Perl là một ngôn ngữ lập trình có mục đích chung và, giống như hầu hết các ngôn ngữ lập trình có mục đích chung, mục đích chính của trích dẫn trong Perl không phải là để bảo vệ các chuỗi, mà là đề cập đến chúng. Đây thực sự là một cách mà hầu hết các ngôn ngữ lập trình tương tự như ngôn ngữ tự nhiên. Trong tiếng Anh, và giả sử bạn có một con chó, "con chó của bạn" là một cụm từ có hai từ, trong khi con chó của bạn là một con chó. Tương tự, trong Perl, '$foo'là một chuỗi, trong khi đó $foolà một cái gì đó có tên $foo.

Tuy nhiên, không giống như mọi ngôn ngữ lập trình có mục đích chung khác, đôi khi Perl cũng sẽ hiểu văn bản không được trích dẫn là đề cập đến một chuỗi - một chuỗi "giống" như nó, theo nghĩa là nó được tạo thành từ cùng một ký tự trong cùng thứ tự. Nó sẽ chỉ cố gắng diễn giải mã theo cách đó nếu đó là một bareword (không $hoặc sigil khác, xem bên dưới), và sau khi nó không thể tìm thấy bất kỳ ý nghĩa nào khác để cung cấp cho nó. Sau đó, nó sẽ coi nó như một chuỗi, trừ khi bạn nói với nó là không bằng cách kích hoạt các hạn chế .

Các biến trong Perl thường bắt đầu bằng một ký tự dấu chấm câu được gọi là sigil , chỉ định loại rộng của biến. Ví dụ, $có nghĩa là vô hướng , @có nghĩa là mảng%có nghĩa là hàm băm . ( Có những người khác. ) Đừng lo lắng nếu bạn thấy khó hiểu (hoặc nhàm chán), bởi vì tôi chỉ đưa ra để nói rằng khi một tên hợp lệ xuất hiện trong chương trình Perl nhưng không được đặt trước bởi một sigil, tên đó được cho là một bareword .

Barewords phục vụ các mục đích khác nhau, nhưng chúng thường biểu thị một hàm dựng sẵn hoặc một chương trình con do người dùng định nghĩa đã được xác định trong chương trình (hoặc trong một mô-đun được sử dụng bởi chương trình). Perl không có các hàm dựng sẵn được gọi là b1hoặc DAT, vì vậy khi trình thông dịch Perl nhìn thấy mã b1.DAT, nó sẽ cố gắng xử lý b1DATnhư tên của các chương trình con. Giả sử không có chương trình con nào được xác định, điều này thất bại. Sau đó, các hạn chế được cung cấp chưa được kích hoạt, nó coi chúng là các chuỗi. Điều này sẽ hoạt động, mặc dù bạn có thực sự dự định điều này xảy ra hay không là phỏng đoán của bất kỳ ai. .Toán tử Perl nối chuỗi , do đó, b1.DATđánh giá chuỗib1DAT. Đó là, b1.DATmột cách xấu để viết một cái gì đó như 'b1' . 'DAT'hoặc "b1" . "DAT".

Bạn có thể tự kiểm tra điều này bằng cách chạy lệnh perl -E 'say b1.DAT', chuyển tập lệnh Perl ngắn say b1.DATcho trình thông dịch Perl, chạy nó, in b1DAT. (Trong lệnh đó, ' 'dấu ngoặc kép nói vỏ để vượt qua say b1.DATnhư một đối số dòng lệnh đơn, nếu không, không gian sẽ gây ra sayb1.DATđược phân tích như lời riêng biệtperlsẽ nhận được chúng như các đối số riêng biệt. perlKhông không thấy có dấu ngoặc kép mình, như các vỏ loại bỏ chúng .)

Nhưng bây giờ, hãy thử viếtuse strict; theo kịch bản Perl trước đây say. Bây giờ nó không thành công với cùng một loại lỗi bạn nhận được từ rename:

$ perl -E 'use strict; say b1.DAT'
Bareword "b1" not allowed while "strict subs" in use at -e line 1.
Bareword "DAT" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.

Điều này xảy ra vì đã use strict;cấm người phiên dịch Perl coi các barewords là chuỗi. Để cấm tính năng đặc biệt này, nó thực sự đủ để kích hoạt chỉ subsgiới hạn. Lệnh này tạo ra các lỗi tương tự như trên:

perl -E 'use strict "subs"; say b1.DAT'

Nhưng thông thường các lập trình viên Perl sẽ chỉ viết use strict;, cho phép subshạn chế và hai người khác. use strict;thường được khuyến khích thực hành. Vì vậy, renamelệnh làm điều này cho mã của bạn . Đó là lý do tại sao bạn nhận được thông báo lỗi đó.

4. Tóm lại, đây là những gì đã xảy ra:

  1. Shell của bạn được truyền b1.DATdưới dạng đối số dòng lệnh đầu tiên, renameđược coi là mã Perl để chạy trong một vòng lặp cho mỗi đối số tên đường dẫn.
  2. Điều này đã được thực hiện có nghĩa là b1DATkết nối với các .nhà điều hành.
  3. b1DATkhông có tiền tố với sigils, vì vậy chúng được coi là barewords.
  4. Hai barewords đó sẽ được coi là tên của các hàm dựng sẵn hoặc bất kỳ chương trình con nào do người dùng định nghĩa, nhưng không có thứ nào có tên đó.
  5. Nếu "subs subs" không được kích hoạt, thì chúng sẽ được xử lý như các biểu thức chuỗi 'b1''DAT'và được nối. Đó là xa những gì bạn dự định, cho thấy tính năng này thường không hữu ích.
  6. Tuy nhiên, "tàu ngầm nghiêm ngặt" được kích hoạt, bởi vì renamecho phép tất cả các hạn chế ( vars, refssubs). Do đó, bạn đã có một lỗi thay thế.
  7. renamebỏ do lỗi này Bởi vì loại lỗi này xảy ra sớm, không có nỗ lực đổi tên tệp nào được thực hiện, mặc dù bạn đã không vượt qua -n. Đây là một điều tốt, thường bảo vệ người dùng khỏi những thay đổi tên tệp không chủ ý và đôi khi thậm chí khỏi mất dữ liệu thực tế.

Cảm ơn Zanna , người đã giúp tôi giải quyết một số thiếu sót quan trọng trong một bản thảo ealier của câu trả lời này . Không có cô ấy, câu trả lời này sẽ trở nên ít ý nghĩa hơn, và có thể không được đăng lên.

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.