Làm cách nào tôi có thể sử dụng các ký tự đại diện nghịch hoặc phủ định khi khớp mẫu trong trình bao unix / linux?


325

Nói rằng tôi muốn sao chép nội dung của một thư mục không bao gồm các tệp và thư mục có tên chứa từ 'Âm nhạc'.

cp [exclude-matches] *Music* /target_directory

Điều gì nên thay thế [loại trừ trận đấu] để thực hiện điều này?

Câu trả lời:


374

Trong Bash, bạn có thể làm điều đó bằng cách bật extglobtùy chọn, như thế này (tất nhiên thay thế lsbằng cpvà thêm thư mục đích)

~/foobar> shopt extglob
extglob        off
~/foobar> ls
abar  afoo  bbar  bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob  # Enables extglob
~/foobar> ls !(b*)
abar  afoo
~/foobar> ls !(a*)
bbar  bfoo
~/foobar> ls !(*foo)
abar  bbar

Sau này bạn có thể vô hiệu hóa extglob với

shopt -u extglob

14
Tôi thích tính năng này:ls /dir/*/!(base*)
Erick Robertson

6
Làm thế nào để bạn bao gồm mọi thứ ( ) và cũng loại trừ! (B )?
Elijah Lynn

4
Làm thế nào bạn sẽ phù hợp, nói, tất cả mọi thứ bắt đầu với f, ngoại trừ foo?
Noldorin

8
Tại sao điều này bị tắt theo mặc định?
weberc2

3
shopt -o -u histExand nếu bạn cần tìm các tệp có dấu chấm than trong đó - theo mặc định, extglob bị tắt theo mặc định để nó không can thiệp vào histrecand, trong tài liệu nó giải thích tại sao điều này lại như vậy. khớp với mọi thứ bắt đầu bằng f ngoại trừ foo: f! (oo), tất nhiên 'thức ăn' vẫn sẽ khớp (bạn sẽ cần f! (oo *) để ngăn chặn những thứ bắt đầu trong 'foo' hoặc, nếu bạn muốn thoát khỏi ! thứ nhất định kết thúc bằng '.foo' sử dụng ( ! .foo), hoặc tiền tố: myprefix ( .foo) (phù hợp với myprefixBLAH nhưng không myprefixBLAH.foo)
osirisgothra

227

Các extglobtùy chọn vỏ mang đến cho bạn phù hợp với mô hình mạnh mẽ hơn trong dòng lệnh.

Bạn bật nó lên shopt -s extglobvà tắt nó đi shopt -u extglob.

Trong ví dụ của bạn, ban đầu bạn sẽ làm:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

Các toán tử bing toàn cầu đã kết thúc ext có sẵn (trích từ ):man bash

Nếu tùy chọn shell extglob được bật bằng cách sử dụng nội dung shopt, một số toán tử khớp mẫu mở rộng được nhận dạng. Danh sách mẫu là danh sách một hoặc nhiều mẫu được phân tách bằng dấu |. Các mẫu tổng hợp có thể được hình thành bằng cách sử dụng một hoặc nhiều mẫu phụ sau:

  • ? (danh sách mẫu)
    Khớp 0 hoặc một lần xuất hiện của các mẫu đã cho
  • * (danh sách mẫu)
    Khớp 0 hoặc nhiều lần xuất hiện của các mẫu đã cho
  • + (danh sách mẫu)
    Khớp với một hoặc nhiều lần xuất hiện của các mẫu đã cho
  • @ (danh sách mẫu)
    Phù hợp với một trong các mẫu đã cho
  • ! (danh sách mẫu)
    Phù hợp với mọi thứ trừ một trong các mẫu đã cho

Vì vậy, ví dụ, nếu bạn muốn liệt kê tất cả các tệp trong thư mục hiện tại không phải .choặc .htệp, bạn sẽ làm:

$ ls -d !(*@(.c|.h))

Tất nhiên, hoạt động hả hê vỏ bình thường, vì vậy ví dụ cuối cùng cũng có thể được viết là:

$ ls -d !(*.[ch])

1
Lý do cho -d là gì?
Big McLUNDHuge

2
@Koveras cho trường hợp một trong các tệp .choặc .hlà một thư mục.
tzot

@DaveKennedy Đó là liệt kê mọi thứ trong thư mục hiện tại D, nhưng không phải là nội dung của các thư mục con có thể có trong thư mục D.
spurra

23

Không phải trong bash (mà tôi biết), nhưng:

cp `ls | grep -v Music` /target_directory

Tôi biết đây không phải là chính xác những gì bạn đang tìm kiếm, nhưng nó sẽ giải quyết ví dụ của bạn.


Ls mặc định sẽ đặt nhiều tệp trên mỗi dòng, có thể sẽ không cho kết quả đúng.
Daniel Bungert

10
Chỉ khi thiết bị xuất chuẩn là thiết bị đầu cuối. Khi được sử dụng trong một đường ống, ls sẽ in một tên tệp trên mỗi dòng.
Adam Rosenfield

ls chỉ đặt nhiều tệp trên mỗi dòng nếu xuất ra thiết bị đầu cuối. Hãy tự thử - "ls | less" sẽ không bao giờ có nhiều tệp trên mỗi dòng.
SpoonMeiser

3
Nó sẽ không hoạt động đối với tên tệp chứa khoảng trắng (hoặc các ký tự khoảng trắng khác).
tzot

7

Nếu bạn muốn tránh chi phí mem khi sử dụng lệnh exec, tôi tin rằng bạn có thể làm tốt hơn với xargs. Tôi nghĩ rằng sau đây là một thay thế hiệu quả hơn để

find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec



find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/

6

Trong bash, một thay thế cho shopt -s extglobGLOBIGNOREbiến . Nó không thực sự tốt hơn, nhưng tôi thấy dễ nhớ hơn.

Một ví dụ có thể là những gì người đăng ban đầu muốn:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

Khi hoàn thành, unset GLOBIGNOREđể có thể rm *techno*trong thư mục nguồn.


5

Bạn cũng có thể sử dụng một forvòng lặp khá đơn giản :

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done

1
Điều này không tìm thấy đệ quy, đó là hành vi khác với những gì OP muốn.
Adam Rosenfield

1
sử dụng -maxdepth 1cho không đệ quy?
avtomaton

Tôi thấy đây là giải pháp sạch nhất mà không phải bật / tắt tùy chọn shell. Tùy chọn -maxdepth sẽ được đề xuất trong bài đăng này để có kết quả cần thiết cho OP, nhưng tất cả phụ thuộc vào những gì bạn đang cố gắng thực hiện.
David Lapointe

Sử dụng findtrong backticks sẽ phá vỡ theo những cách khó chịu nếu nó tìm thấy bất kỳ tên tệp không cần thiết.
tripleee

5

Sở thích cá nhân của tôi là sử dụng grep và lệnh while. Điều này cho phép một người viết các kịch bản mạnh mẽ nhưng có thể đọc được, đảm bảo rằng bạn sẽ thực hiện chính xác những gì bạn muốn. Ngoài ra, bằng cách sử dụng lệnh echo, bạn có thể thực hiện chạy khô trước khi thực hiện thao tác thực tế. Ví dụ:

ls | grep -v "Music" | while read filename
do
echo $filename
done

sẽ in ra các tập tin mà bạn sẽ sao chép. Nếu danh sách đúng, bước tiếp theo chỉ đơn giản là thay thế lệnh echo bằng lệnh sao chép như sau:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done

1
Điều này sẽ hoạt động miễn là tên tệp của bạn không có bất kỳ tab, dòng mới nào, nhiều hơn một khoảng trắng trong một hàng hoặc bất kỳ dấu gạch chéo ngược nào. Trong khi đó là những trường hợp bệnh lý, thật tốt khi nhận thức được khả năng này. Trong bashbạn có thể sử dụng while IFS='' read -r filename, nhưng sau đó các dòng mới vẫn là một vấn đề. Nói chung, tốt nhất là không sử dụng lsđể liệt kê các tập tin; công cụ như findlà phù hợp hơn nhiều.
Thedward

Không có bất kỳ công cụ bổ sung nào:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward 14/03/13

mywiki.wooledge.org/ParsingLs liệt kê một số lý do bổ sung tại sao bạn nên tránh điều này.
tripleee

5

Một thủ thuật tôi đã không nhìn thấy trên đây được nêu ra mà không sử dụng extglob, findhoặc greplà để điều trị hai danh sách tập tin như bộ và "diff" chúng bằng comm:

comm -23 <(ls) <(ls *Music*)

commđược ưa thích hơn diffbởi vì nó không có thêm hành trình.

Lợi nhuận này tất cả các yếu tố của bộ 1, ls, đó là không còn ở set 2, ls *Music*. Điều này đòi hỏi cả hai bộ phải được sắp xếp để hoạt động đúng. Không có vấn đề gì đối với lsviệc mở rộng toàn cầu, nhưng nếu bạn đang sử dụng một cái gì đó như thế find, hãy chắc chắn gọi sort.

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

Có khả năng hữu ích.


1
Một trong những lợi ích của việc loại trừ là không đi qua thư mục ở vị trí đầu tiên. Giải pháp này thực hiện hai lần duyệt qua các thư mục con - một với loại trừ và một không có.
Mark Stosberg

Điểm rất tốt, @MarkStosberg. Mặc dù, một lợi ích rìa của kỹ thuật này là bạn có thể đọc loại trừ từ một tập tin thực tế, ví dụ nhưcomm -23 <(ls) exclude_these.list
James M. Lay

3

Một giải pháp cho điều này có thể được tìm thấy với find.

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

Tìm có khá nhiều tùy chọn, bạn có thể nhận được khá cụ thể về những gì bạn bao gồm và loại trừ.

Chỉnh sửa: Adam trong các ý kiến ​​lưu ý rằng điều này là đệ quy. tìm tùy chọn mindepth và maxdepth có thể hữu ích trong việc kiểm soát điều này.


Đây là một bản sao đệ quy, đó là hành vi khác nhau. Nó cũng tạo ra một quy trình mới cho mỗi tệp, có thể rất kém hiệu quả đối với một số lượng lớn tệp.
Adam Rosenfield

Chi phí sinh ra một quá trình xấp xỉ bằng 0 so với tất cả các IO sao chép mỗi tệp tạo ra. Vì vậy, tôi muốn nói rằng điều này là đủ tốt để sử dụng thường xuyên.
dland

Một số cách giải quyết cho quá trình sinh sản: stackoverflow.com/questions/186099/NH
Vinko Vrsalovic

sử dụng "-maxdepth 1" để tránh đệ quy.
ejgottl

sử dụng backticks để có được sự tương tự của việc mở rộng thẻ hoang dã shell: cp find -maxdepth 1 -not -name '*Music*'/ target_directory
ejgottl

2

Các tác phẩm sau đây liệt kê tất cả *.txtcác tệp trong thư mục hiện tại, ngoại trừ những tệp bắt đầu bằng một số.

Này hoạt động trong bash, dash, zshvà tất cả vỏ tương thích khác POSIX.

for FILE in /some/dir/*.txt; do    # for each *.txt file
    case "${FILE##*/}" in          #   if file basename...
        [0-9]*) continue ;;        #   starts with digit: skip
    esac
    ## otherwise, do stuff with $FILE here
done
  1. Trong dòng một, mẫu /some/dir/*.txtsẽ khiến forvòng lặp lặp lại trên tất cả các tệp /some/dircó tên kết thúc bằng .txt.

  2. Trong dòng hai, một câu lệnh tình huống được sử dụng để loại bỏ các tệp không mong muốn. - ${FILE##*/}Biểu thức loại bỏ bất kỳ thành phần tên thư mục hàng đầu nào khỏi tên tệp (ở đây /some/dir/) để các phần tử có thể khớp với chỉ tên cơ sở của tệp. (Nếu bạn chỉ loại bỏ tên tệp dựa trên hậu tố, bạn có thể rút ngắn tên này thành $FILEthay thế.)

  3. Trong dòng ba, tất cả các tệp khớp với casemẫu [0-9]*) sẽ bị bỏ qua ( continuecâu lệnh nhảy sang lần lặp tiếp theo của forvòng lặp). - Nếu bạn muốn, bạn có thể làm điều gì đó thú vị hơn ở đây, ví dụ như bỏ qua tất cả các tệp không bắt đầu bằng một chữ cái (một Z z) bằng cách sử dụng [!a-z]*hoặc bạn có thể sử dụng nhiều mẫu để bỏ qua một số loại tên tệp, ví dụ như [0-9]*|*.bakbỏ qua các tệp cả hai .baktệp và các tệp không bắt đầu bằng số.


Đừng! Có một lỗi (tôi khớp với *.txtthay vì chỉ *). Đã sửa bây giờ.
zrajm

0

điều này sẽ làm điều đó không bao gồm chính xác 'Âm nhạc'

cp -a ^'Music' /target

cái này và cái kia để loại trừ những thứ như Âm nhạc? * hoặc *? Âm nhạc

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target

Các cptrang hướng dẫn trên hệ điều hành MacOS có một -alựa chọn nhưng nó làm điều gì đó hoàn toàn khác nhau. Nền tảng nào hỗ trợ điều này?
tripleee
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.