2009-12-12 30 views
257

Tôi có tập lệnh nhỏ này trong sh (Mac OSX 10.6) để xem qua một loạt tệp. Google đã ngừng việc hữu ích vào thời điểm này:Chụp nhóm từ một Grep RegEx

files="*.jpg" 
for f in $files 
    do 
     echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*' 
     name=$? 
     echo $name 
    done 

Cho đến nay (rõ ràng, để bạn shell gurus) $name chỉ nắm giữ 0, 1 hoặc 2, tùy thuộc vào nếu grep thấy rằng tên tập tin phù hợp với các vấn đề cung cấp. Điều tôi muốn là chụp những gì bên trong các parens ([a-z]+) và lưu nó vào một biến.

Tôi muốn chỉ sử dụng grep, nếu có thể. Nếu không, xin vui lòng không có Python hoặc Perl, vv sed hoặc một cái gì đó như nó - Tôi mới để vỏ và muốn tấn công này từ góc * purist * nix.

Ngoài ra, dưới dạng bonu siêu mát mẻ s, tôi tò mò về cách tôi có thể nối chuỗi trong vỏ? Nhóm tôi đã chụp là chuỗi "somename" được lưu trữ trong $ name, và tôi muốn thêm chuỗi ".jpg" vào cuối của nó, tôi có thể cat $name '.jpg' không?

Vui lòng giải thích những gì đang xảy ra nếu bạn có thời gian.

+20

Là grep * thực sự * unix tinh khiết hơn sed? –

+1

Ah, không có ý muốn đề nghị điều đó. Tôi chỉ hy vọng rằng một giải pháp có thể được tìm thấy bằng cách sử dụng một công cụ tôi đặc biệt đang cố gắng tìm hiểu ở đây. Nếu không thể giải quyết bằng 'grep', thì' sed' sẽ tuyệt vời, nếu có thể giải quyết bằng cách sử dụng 'sed'. – Isaac

+2

Tôi cần phải đặt một :) trên btw đó ... –

Trả lời

344

Nếu bạn đang sử dụng Bash, bạn thậm chí không phải sử dụng grep:

files="*.jpg" 
regex="[0-9]+_([a-z]+)_[0-9a-z]*" 
for f in $files 
do 
    if [[ $f =~ $regex ]] 
    then 
     name="${BASH_REMATCH[1]}" 
     echo "${name}.jpg" # concatenate strings 
     name="${name}.jpg" # same thing stored in a variable 
    else 
     echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files 
    fi 
done 

Nó tốt hơn để đưa các regex trong một biến. Một số mẫu sẽ không hoạt động nếu được bao gồm theo nghĩa đen.

Điều này sử dụng =~ là toán tử đối sánh regex của Bash. Kết quả của trận đấu được lưu vào một mảng gọi là $BASH_REMATCH. Nhóm chụp đầu tiên được lưu trữ trong chỉ mục 1, nhóm thứ hai (nếu có) trong chỉ mục 2, vv Chỉ số 0 là toàn bộ kết quả.

Bạn nên biết rằng nếu không có neo, regex này (và một trong những sử dụng grep) sẽ phù hợp với bất kỳ ví dụ sau đây và nhiều hơn nữa, mà có thể không được những gì bạn đang tìm kiếm:

123_abc_d4e5 
xyz123_abc_d4e5 
123_abc_d4e5.xyz 
xyz123_abc_d4e5.xyz 

Để loại bỏ các ví dụ thứ hai và thứ tư, hãy regex của bạn như thế này:

^[0-9]+_([a-z]+)_[0-9a-z]* 

mà nói rằng chuỗi phải bắt đầu với một hoặc nhiều chữ số. Carat đại diện cho sự bắt đầu của chuỗi. Nếu bạn thêm một dấu đô la vào cuối regex, như thế này:

^[0-9]+_([a-z]+)_[0-9a-z]*$ 

sau đó ví dụ thứ ba cũng sẽ được loại bỏ kể từ khi chấm không là một trong những nhân vật trong regex và ký hiệu đô la đại diện cuối chuỗi. Lưu ý rằng ví dụ thứ tư cũng không khớp được.

Nếu bạn có GNU grep (khoảng 2,5 hoặc sau đó, tôi nghĩ rằng, khi các nhà điều hành \K đã được bổ sung):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg 

Các \K điều hành (chiều dài thay đổi nhìn đằng sau) gây ra các mô hình trước để phù hợp với , nhưng không bao gồm kết quả phù hợp trong kết quả. Độ dài tương đương cố định là (?<=) - mẫu sẽ được bao gồm trước dấu ngoặc đơn đóng. Bạn phải sử dụng \K nếu số lượng có thể khớp với các chuỗi có độ dài khác nhau (ví dụ: +, *, {2,4}).

Toán tử (?=) khớp với các mẫu cố định hoặc có độ dài thay đổi và được gọi là "nhìn về phía trước". Nó cũng không bao gồm chuỗi phù hợp trong kết quả.

Để làm cho khớp không phân biệt chữ hoa chữ thường, toán tử (?i) được sử dụng. Nó ảnh hưởng đến các mô hình theo nó để vị trí của nó là đáng kể.

Regex có thể cần được điều chỉnh tùy thuộc vào việc có các ký tự khác trong tên tệp hay không. Bạn sẽ lưu ý rằng trong trường hợp này, tôi hiển thị một ví dụ về ghép nối một chuỗi cùng một lúc mà chuỗi con được bắt giữ.

+23

Trong câu trả lời này, tôi muốn upvote dòng cụ thể nói rằng "Tốt hơn nên đặt regex trong một biến. Một số mẫu sẽ không hoạt động nếu được bao gồm theo nghĩa đen." – Brandin

+0

"Tốt hơn nên đặt regex trong một biến. Một số mẫu sẽ không hoạt động nếu được bao gồm theo nghĩa đen." - Tại sao nó lại xảy ra? Có cách nào khắc phục chúng không? –

+2

@FrancescoFrassinelli: Ví dụ là mẫu bao gồm khoảng trắng. Thật khó xử để trốn thoát và bạn không thể sử dụng dấu ngoặc kép vì điều đó buộc nó từ một regex đến một chuỗi bình thường. Cách chính xác để làm điều đó là sử dụng một biến. Báo giá có thể được sử dụng trong quá trình phân công khiến mọi việc trở nên đơn giản hơn nhiều. –

18

Không thể chỉ trong grep Tôi tin

cho sed:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'` 

tôi sẽ mất một đâm vào tiền thưởng mặc dù:

echo "$name.jpg" 
+0

Ah, tất nhiên, cảm ơn vì điều đó haha. – Isaac

+2

Thật không may, giải pháp 'sed' đó không hoạt động. Nó chỉ đơn giản là in ra tất cả mọi thứ trong thư mục của tôi. – Isaac

+0

được cập nhật, sẽ xuất ra một dòng trống nếu không có kết quả phù hợp, vì vậy hãy chắc chắn kiểm tra xem – cobbal

1

Một gợi ý cho bạn - bạn có thể sử dụng mở rộng tham số để xóa phần tên khỏi dấu gạch dưới cuối cùng trở đi và tương tự ở đầu:

f=001_abc_0za.jpg 
work=${f%_*} 
name=${work#*_} 

Sau đó, name sẽ có giá trị abc.

Xem Apple developer docs, tìm kiếm về 'Mở rộng tham số'.

+1

Ah, bây giờ điều này không hoạt động. Nhưng nó có * unix-y * đủ không? Hmm ... – Isaac

+0

điều này sẽ không kiểm tra ([a-z] +). – ghostdog74

+0

@levislevis - đó là sự thật, nhưng, như nhận xét của OP, nó làm những gì cần thiết. –

112

Điều này là không thực sự có thể với tinh khiết grep, ít nhất là không nói chung.

Nhưng nếu mẫu của bạn phù hợp, bạn có thể sử dụng grep nhiều lần trong một đường ống để đầu tiên giảm dòng của bạn thành định dạng đã biết và sau đó trích xuất chỉ một chút mà bạn muốn. (Mặc dù các công cụ như cutsed là tốt hơn nhiều ở đây).

Giả sử vì lợi ích của lập luận rằng mô hình của bạn là một chút đơn giản hơn: [0-9]+_([a-z]+)_ Bạn có thể trích xuất này như sau:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+' 

Các grep đầu tiên sẽ loại bỏ bất kỳ dòng mà không phù hợp với patern tổng thể của bạn, thứ hai grep (trong đó có --only-matching được chỉ định) sẽ hiển thị phần alpha của tên. Điều này chỉ hoạt động vì mẫu phù hợp: "phần alpha" là đủ cụ thể để rút ra những gì bạn muốn.

(Ngoài: Cá nhân tôi muốn sử dụng grep + cut để đạt được những gì bạn đang theo sau: echo $name | grep {pattern} | cut -d _ -f 2.Điều này được cut để phân tích cú pháp dòng thành các trường bằng cách chia tách trên dấu phân tách _ và trả về trường chỉ 2 (số trường bắt đầu tại 1)).

Triết lý Unix là có công cụ làm một việc, và làm tốt, và kết hợp chúng để đạt được các nhiệm vụ không tầm thường, vì vậy tôi cho rằng grep + sed v.v ... là cách thực hiện Unixy: -)

+2

'cho f trong $ tệp; do name = 'echo $ f | grep -oEi '[0-9] + _ ([a-z] +) _ [0-9a-z] *' | cut -d _ -f 2'; 'Aha! – Isaac

+1

sử dụng vỏ, không cần cắt grep +. lãng phí chi phí nếu OP có rất nhiều tập tin .. – ghostdog74

+2

tôi không đồng ý với "triết lý" đó. nếu bạn có thể sử dụng trình bao trong các khả năng được xây dựng mà không cần gọi các lệnh bên ngoài, thì kịch bản của bạn sẽ nhanh hơn rất nhiều về hiệu năng. có một số công cụ chồng chéo trong hàm. ví dụ như grep và sed và awk. tất cả chúng đều thao túng chuỗi, nhưng awk đứng trên tất cả vì nó có thể làm nhiều hơn nữa. Thực tế, tất cả những chuỗi lệnh đó, giống như hai greps trên hoặc grep + sed có thể được rút ngắn bằng cách thực hiện chúng với một quá trình awk. – ghostdog74

1

nếu bạn có bash, bạn có thể sử dụng mở rộng globbing

shopt -s extglob 
shopt -s nullglob 
shopt -s nocaseglob 
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg 
do 
    IFS="_" 
    set -- $file 
    echo "This is your captured output : $2" 
done 

hoặc

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file 
do 
    IFS="_" 
    set -- $file 
    echo "This is your captured output : $2" 
done 
+0

Điều đó có vẻ hấp dẫn. Có lẽ bạn có thể nối thêm một chút giải thích cho nó? Hoặc, nếu bạn nghiêng, liên kết với một nguồn tài nguyên đặc biệt sâu sắc giải thích nó? Cảm ơn! – Isaac

+0

hướng dẫn tham khảo bash - 3.5.8.1 So khớp mẫu – ghostdog74

+1

quên liên kết: tại đây http://www.gnu.org/software/bash/manual/bashref.html – ghostdog74

9

Đây là một giải pháp mà sử dụng gawk. Đó là một cái gì đó tôi thấy tôi cần phải sử dụng thường xuyên vì vậy tôi đã tạo ra một chức năng cho nó

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; } 

sử dụng chỉ làm

$ echo 'hello world' | regex1 'hello\s(.*)' 
world 
67

Tôi nhận ra rằng một câu trả lời đã được chấp nhận cho điều này, nhưng từ một "Nghiêm * Nix góc thuần túy "có vẻ như công cụ thích hợp cho công việc là pcregrep, có vẻ như chưa được đề cập đến. Hãy thử thay đổi dòng:

echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*' 
    name=$? 

như sau:

name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*') 

để có được chỉ các nội dung của nhóm chụp 1.

Công cụ pcregrep sử dụng tất cả các cú pháp tương tự như bạn đã sử dụng với grep, nhưng thực hiện chức năng mà bạn cần.

Tham số -o hoạt động giống như các phiên bản grep nếu nó là trần, nhưng nó cũng chấp nhận một tham số số trong pcregrep, mà chỉ ra đó chụp nhóm bạn muốn hiển thị.

Với giải pháp này, có tối thiểu thay đổi cần thiết trong tập lệnh. Bạn chỉ cần thay thế một tiện ích mô-đun với một tiện ích khác và tinh chỉnh các tham số.

Lưu ý thú vị: Bạn có thể sử dụng nhiều đối số -o để trả về nhiều nhóm chụp theo thứ tự xuất hiện trên dòng.

+3

'pcregrep' không có sẵn theo mặc định trong 'Mac OS X' là những gì OP sử dụng – grebneke

+1

+1 cho một lớp lót –

+4

My' pcregrep' dường như không hiểu chữ số sau '-o':" Không có tùy chọn chữ '1' trong "-o1". Ngoài ra không có đề cập đến chức năng đó khi nhìn vào 'pcregrep --help' –

Các vấn đề liên quan