Tìm kiếm và thay thế trong bash bằng các biểu thức thông thường


160

Tôi đã thấy ví dụ này:

hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//[0-9]/}

Theo cú pháp sau: ${variable//pattern/replacement}

Thật không may, patterntrường dường như không hỗ trợ cú pháp regex đầy đủ (nếu tôi sử dụng .hoặc\s , ví dụ, nó cố gắng khớp các ký tự bằng chữ).

Làm cách nào tôi có thể tìm kiếm / thay thế một chuỗi bằng cú pháp regex đầy đủ?


Tìm thấy một câu hỏi liên quan ở đây: stackoverflow.com/questions/5658085/ từ
jheddings

2
FYI, \skhông phải là một phần của cú pháp biểu thức chính quy định chuẩn POSIX (không phải BRE hoặc ERE); đó là một phần mở rộng PCRE và hầu như không có sẵn từ shell. [[:space:]]là tương đương phổ quát hơn.
Charles Duffy

1
\scó thể được thay thế bằng [[:space:]], bằng cách, .bởi ?, và các phần mở rộng extglob cho ngôn ngữ mẫu vỏ cơ sở có thể được sử dụng cho những thứ như các nhóm con tùy chọn, các nhóm lặp lại và tương tự.
Charles Duffy


Tôi sử dụng điều này trong phiên bản bash 4.1.11 trên Solaris ... echo $ {hello // [0-9]} Lưu ý việc thiếu dấu gạch chéo cuối cùng.
Daniel Liston

Câu trả lời:


175

Sử dụng sed :

MYVAR=ho02123ware38384you443d34o3434ingtod38384day
echo "$MYVAR" | sed -e 's/[a-zA-Z]/X/g' -e 's/[0-9]/N/g'
# prints XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Lưu ý rằng các lần tiếp theo -eđược xử lý theo thứ tự. Ngoài ra, gcờ cho biểu thức sẽ khớp với tất cả các lần xuất hiện trong đầu vào.

Bạn cũng có thể chọn công cụ yêu thích của mình bằng phương pháp này, ví dụ: perl, awk, vd:

echo "$MYVAR" | perl -pe 's/[a-zA-Z]/X/g and s/[0-9]/N/g'

Điều này có thể cho phép bạn thực hiện nhiều kết quả sáng tạo hơn ... Ví dụ: trong đoạn trích ở trên, thay thế số sẽ không được sử dụng trừ khi có kết quả khớp trên biểu thức đầu tiên (do andđánh giá lười biếng ). Và tất nhiên, bạn có sự hỗ trợ ngôn ngữ đầy đủ của Perl để thực hiện đấu thầu ...


Điều này chỉ làm một thay thế duy nhất như tôi có thể nói. Có cách nào để nó thay thế tất cả các lần xuất hiện của mẫu như những gì mã tôi đã đăng không?
Lanaru

Tôi đã cập nhật câu trả lời của mình để chứng minh nhiều sự thay thế cũng như khớp mẫu toàn cầu. Hãy cho tôi biết nếu nó giúp được bạn.
cưới

Cám ơn rất nhiều! Vì tò mò, tại sao bạn lại chuyển từ phiên bản một dòng (trong câu trả lời ban đầu của bạn) sang hai dòng?
Lanaru

9
Sử dụng sedhoặc các công cụ bên ngoài khác là tốn kém do thời gian khởi tạo quy trình. Tôi đặc biệt tìm kiếm giải pháp all-bash, vì tôi thấy việc sử dụng thay thế bash nhanh hơn gấp 3 lần so với cách gọi sedcho mỗi mục trong vòng lặp của tôi.
rr-

6
@CiroSantilli 六四 事件 法轮功, được cho là, đó là sự khôn ngoan thông thường, nhưng điều đó không làm cho nó trở nên khôn ngoan. Đúng, bash rất chậm dù thế nào đi chăng nữa - nhưng bash được viết tốt để tránh các subshells thực sự là các đơn đặt hàng có cường độ nhanh hơn bash gọi các công cụ bên ngoài cho mọi nhiệm vụ nhỏ. Ngoài ra, các tập lệnh shell được viết tốt sẽ được hưởng lợi từ các trình thông dịch nhanh hơn (như ksh93, có hiệu suất ngang với awk), trong khi các tập lệnh được viết kém thì không có gì để làm.
Charles Duffy

133

Điều này thực sự có thể được thực hiện trong bash tinh khiết:

hello=ho02123ware38384you443d34o3434ingtod38384day
re='(.*)[0-9]+(.*)'
while [[ $hello =~ $re ]]; do
  hello=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
done
echo "$hello"

... mang lại ...

howareyoudoingtodday

2
Một cái gì đó cho tôi biết bạn sẽ thích những thứ này: stackoverflow.com/questions/5624969/
mẹo

=~là chìa khóa Nhưng một chút lộn xộn, đưa ra sự phân công lại trong vòng lặp. @jheddings giải pháp 2 năm trước là một lựa chọn tốt khác - gọi sed hoặc perl).
Brent Faust

3
Gọi điện thoại sedhoặcperl là hợp lý, nếu sử dụng mỗi lời gọi để xử lý nhiều hơn một dòng đầu vào. Gọi một công cụ như vậy ở bên trong một vòng lặp, trái ngược với việc sử dụng một vòng lặp để xử lý luồng đầu ra của nó, thật là ngu ngốc.
Charles Duffy

2
FYI, trong zsh, nó chỉ $matchthay vì $BASH_REMATCH. (Bạn có thể làm cho nó hoạt động giống như bash với setopt bash_rematch.)
Marian

Thật kỳ quặc - bởi vì zsh không cố gắng trở thành một vỏ POSIX, nên được cho là tuân theo thư hướng dẫn POSIX về tất cả các biến mũ được sử dụng cho các mục đích POSIX (có liên quan đến hệ thống) và các biến chữ thường được dành riêng cho sử dụng ứng dụng. Nhưng inasmuch như zsh là một cái gì đó chạy các ứng dụng, chứ không phải là một ứng dụng, quyết định này sử dụng không gian tên biến ứng dụng thay vì không gian tên hệ thống có vẻ hết sức sai lầm.
Charles Duffy

94

Những ví dụ này cũng hoạt động trong bash không cần sử dụng sed:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[a-zA-Z]/X} 
echo ${MYVAR//[0-9]/N}

bạn cũng có thể sử dụng các biểu thức khung lớp ký tự

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day
MYVAR=${MYVAR//[[:alpha:]]/X} 
echo ${MYVAR//[[:digit:]]/N}

đầu ra

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

Tuy nhiên, điều mà @Lanaru muốn biết, nếu tôi hiểu chính xác câu hỏi, là tại sao các phần mở rộng "đầy đủ" hoặc PCRE \s\S\w\W\d\D v.v. không hoạt động như được hỗ trợ trong php ruby ​​python, v.v. Những phần mở rộng này là từ các biểu thức chính quy tương thích Perl (PCRE) và có thể không tương thích với các dạng biểu thức chính quy dựa trên shell khác.

Chúng không hoạt động:

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo ${hello//\d/}


#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | sed 's/\d//g'

đầu ra với tất cả các ký tự "d" được loại bỏ

ho02123ware38384you44334o3434ingto38384ay

nhưng sau đây không hoạt động như mong đợi

#!/bin/bash
hello=ho02123ware38384you443d34o3434ingtod38384day
echo $hello | perl -pe 's/\d//g'

đầu ra

howareyoudoingtodday

Hy vọng rằng sẽ làm rõ mọi thứ hơn một chút nhưng nếu bạn chưa nhầm lẫn tại sao bạn không thử điều này trên Mac OS X có cờ REG_ENHANCED được bật:

#!/bin/bash
MYVAR=ho02123ware38384you443d34o3434ingtod38384day;
echo $MYVAR | grep -o -E '\d'

Trên hầu hết các hương vị của * nix, bạn sẽ chỉ thấy đầu ra sau:

d
d
d

Xin chào!


6
Ân xá? không phải${foo//$bar/$baz} là cú pháp POSIX.2 BRE hoặc ERE - phù hợp với kiểu mẫu fnmatch ().
Charles Duffy

8
... vì vậy, trong khi ${hello//[[:digit:]]/}hoạt động, nếu chúng ta chỉ muốn lọc ra các chữ số đứng trước chữ cái o, ${hello//o[[:digit:]]*}sẽ có hành vi hoàn toàn khác với hành vi dự kiến ​​(vì trong các mẫu fnmatch, *khớp với tất cả các ký tự, thay vì sửa đổi mục ngay trước đó thành 0 trở lên).
Charles Duffy

1
Xem pubs.opengroup.org/onlinepub/9699919799/utilities/ ((và tất cả những gì nó kết hợp bằng cách tham khảo) để biết thông số đầy đủ về fnmatch.
Charles Duffy

1
man bash: Một toán tử nhị phân bổ sung, = ~, có sẵn, với cùng mức ưu tiên là == và! =. Khi nó được sử dụng, chuỗi bên phải của toán tử được coi là một biểu thức chính quy mở rộng và khớp với nhau (như trong regex (3)).
nickl-

1
@aderchox bạn đúng, đối với các chữ số bạn có thể sử dụng [0-9]hoặc[[:digit:]]
nickl- 17/07/19

13

Nếu bạn đang thực hiện các cuộc gọi lặp đi lặp lại và quan tâm đến hiệu suất, Thử nghiệm này cho thấy phương pháp BASH nhanh hơn ~ 15 lần so với việc chuyển sang sed và có thể là bất kỳ quy trình bên ngoài nào khác.

hello=123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X123456789X

P1=$(date +%s)

for i in {1..10000}
do
   echo $hello | sed s/X//g > /dev/null
done

P2=$(date +%s)
echo $[$P2-$P1]

for i in {1..10000}
do
   echo ${hello//X/} > /dev/null
done

P3=$(date +%s)
echo $[$P3-$P2]

1
Nếu bạn quan tâm đến cách giảm dĩa, hãy tìm từ newConnector trong câu trả lời này để Làm thế nào để đặt biến thành đầu ra của lệnh trong Bash?
F. Hauri

8

Sử dụng [[:digit:]](lưu ý dấu ngoặc kép) làm mẫu:

$ hello=ho02123ware38384you443d34o3434ingtod38384day
$ echo ${hello//[[:digit:]]/}
howareyoudoingtodday

Chỉ muốn tóm tắt các câu trả lời (đặc biệt là @ nickl-'s https://stackoverflow.com/a/22261334/2916086 ).

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.