ค้นหาและแทนที่ด้วย bash โดยใช้นิพจน์ทั่วไป


161

ฉันเคยเห็นตัวอย่างนี้:

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

ซึ่งตามไวยากรณ์นี้: ${variable//pattern/replacement}

น่าเสียดายที่patternเขตข้อมูลนั้นดูเหมือนจะไม่สนับสนุนไวยากรณ์แบบเต็มของ regex (ถ้าฉันใช้.หรือ\sตัวอย่างเช่นมันพยายามจับคู่อักขระตามตัวอักษร)

ฉันจะค้นหา / แทนที่สตริงโดยใช้ไวยากรณ์ regex แบบเต็มได้อย่างไร


พบคำถามที่เกี่ยวข้องที่นี่: stackoverflow.com/questions/5658085/…
jheddings

2
FYI \sไม่ใช่ส่วนหนึ่งของไวยากรณ์นิพจน์ปกติที่กำหนด POSIX มาตรฐาน (ไม่ใช่ BRE หรือ ERE) มันเป็นส่วนขยาย PCRE และส่วนใหญ่ไม่สามารถใช้ได้จากเชลล์ [[:space:]]เทียบเท่าสากลมากขึ้น
Charles Duffy

1
\sสามารถแทนที่ด้วย[[:space:]], โดยวิธี, .โดย?, และส่วนขยาย extglob ไปยังภาษารูปแบบเชลล์พื้นฐานสามารถใช้สำหรับสิ่งต่าง ๆ เช่นกลุ่มย่อยทางเลือก, กลุ่มซ้ำ, และสิ่งที่คล้ายกัน
Charles Duffy

3
รายละเอียดของรูปแบบการทุบตี
เซเว่น

ฉันใช้สิ่งนี้ใน bash เวอร์ชัน 4.1.11 บน Solaris ... echo $ {hello // [0-9]} สังเกตว่าไม่มีเครื่องหมายทับสุดท้าย
Daniel Liston

คำตอบ:


175

ใช้sed :

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

โปรดทราบว่า-eประมวลผลลำดับต่อมาตามลำดับ นอกจากนี้gแฟล็กสำหรับนิพจน์จะตรงกับสิ่งที่เกิดขึ้นทั้งหมดในอินพุต

นอกจากนี้คุณยังสามารถเลือกเครื่องมือที่คุณชื่นชอบโดยใช้วิธีนี้เช่น perl, awk เช่น:

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

วิธีนี้อาจช่วยให้คุณสามารถจับคู่โฆษณาได้มากขึ้น ... ตัวอย่างเช่นใน snip ด้านบนการแทนที่ตัวเลขจะไม่ถูกใช้จนกว่าจะมีการจับคู่ในนิพจน์แรก (เนื่องจากการandประเมินผลที่ขี้เกียจ) และแน่นอนคุณได้รับการสนับสนุนอย่างเต็มที่จาก Perl ในการเสนอราคา ...


สิ่งนี้จะแทนที่สิ่งเดียวเท่าที่ฉันสามารถบอกได้ มีวิธีให้มันแทนที่รูปแบบที่เกิดขึ้นทั้งหมดเหมือนกับรหัสที่ฉันโพสต์หรือไม่?
Lanaru

ฉันได้อัปเดตคำตอบของฉันเพื่อแสดงการแทนที่หลายรายการรวมถึงการจับคู่รูปแบบทั่วโลก แจ้งให้เราทราบหากช่วยได้
jheddings

ขอบคุณมาก! ด้วยความอยากรู้ทำไมคุณเปลี่ยนจากเวอร์ชั่นหนึ่งบรรทัด (ในคำตอบเดิมของคุณ) เป็นสองซับ
Lanaru

9
การใช้sedหรือเครื่องมือภายนอกอื่น ๆ มีราคาแพงเนื่องจากเวลาในการเริ่มต้นกระบวนการ ฉันค้นหาวิธีการแก้ปัญหาทั้งหมดโดยเฉพาะอย่างยิ่งเพราะฉันพบว่าการใช้การแทนที่ bash นั้นเร็วกว่าการเรียกsedหาแต่ละรายการในลูปของฉันมากกว่า 3 เท่า
rr-

6
@CiroSantilli granted 事件法轮功纳米比亚威视ได้รับนั่นคือภูมิปัญญาทั่วไป แต่นั่นไม่ได้ทำให้มันฉลาด ใช่ทุบตีช้าไม่ว่าอะไร - แต่ทุบตีอย่างดีที่หลีกเลี่ยง subshells แท้จริงคำสั่งของขนาดเร็วกว่าทุบตีที่เรียกเครื่องมือภายนอกสำหรับทุก ๆ งานเล็ก ๆ น้อย ๆ นอกจากนี้เชลล์สคริปต์ที่เขียนอย่างดีจะได้รับประโยชน์จากล่ามที่เร็วขึ้น (เช่น ksh93 ซึ่งมีประสิทธิภาพเทียบเท่ากับ awk) ในขณะที่สคริปต์ที่เขียนไม่ดีจะไม่มีอะไรต้องทำ
ชาร์ลส์ดัฟฟี่

133

สามารถทำได้จริงในทุบตีบริสุทธิ์:

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

... อัตราผลตอบแทน ...

howareyoudoingtodday

2
มีบางอย่างบอกฉันว่าคุณจะรักสิ่งเหล่านี้: stackoverflow.com/questions/5624969/… =)
nickl-

=~เป็นกุญแจสำคัญ แต่มีความกล้าเล็กน้อยที่ได้รับมอบหมายใหม่ในลูป @jheddings โซลูชั่น 2 ปีก่อนเป็นอีกตัวเลือกที่ดี - โทร sed หรือ perl)
Brent Faust

3
การโทรsedหรือperlสมเหตุสมผลถ้าใช้การเรียกใช้แต่ละครั้งเพื่อประมวลผลมากกว่าอินพุตบรรทัดเดียว การเรียกใช้เครื่องมือดังกล่าวที่ด้านในของลูปเมื่อเทียบกับการใช้ลูปเพื่อประมวลผลสตรีมเอาต์พุตนั้นเป็นสิ่งที่ไร้สาระ
Charles Duffy

2
FYI ใน zsh ก็แค่แทน$match $BASH_REMATCH(คุณสามารถทำให้มันทำตัวเหมือนทุบตีด้วยsetopt bash_rematch).
แม

มันแปลก - เนื่องจาก zsh ไม่ได้พยายามเป็น POSIX เชลล์มันก็ตามตัวอักษรของคำแนะนำ POSIX เกี่ยวกับตัวแปร all-caps ที่ใช้สำหรับวัตถุประสงค์ POSIX (เชลล์หรือเกี่ยวข้องกับระบบ) และตัวแปรตัวพิมพ์เล็กถูกสงวนไว้สำหรับ การใช้แอปพลิเคชัน แต่เนื่องจาก zsh เป็นสิ่งที่เรียกใช้แอปพลิเคชันแทนที่จะเป็นแอพพลิเคชั่นเองการตัดสินใจใช้แอปพลิเคชันตัวแปรเนมสเปซมากกว่าที่ระบบจะดูเหมือนว่าเนมสเปซผิดเพี้ยนไปอย่างมาก
Charles Duffy

95

ตัวอย่างเหล่านี้ยังทำงานใน bash ไม่จำเป็นต้องใช้ sed:

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

คุณยังสามารถใช้นิพจน์วงเล็บเหลี่ยมของอักขระ

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

เอาท์พุต

XXNNNNNXXXXNNNNNXXXNNNXNNXNNNNXXXXXXNNNNNXXX

สิ่งที่ @Lanaru ต้องการทราบอย่างไรก็ตามถ้าฉันเข้าใจคำถามอย่างถูกต้องนั่นคือสาเหตุที่ส่วนขยาย "เต็ม" หรือ PCRE \s\S\w\W\d\Dไม่ทำงานอย่างที่สนับสนุนใน php ruby ​​python ฯลฯ ส่วนขยายเหล่านี้มาจากนิพจน์ทั่วไปที่เข้ากันได้กับ Perl (PCRE) และ Perl อาจเข้ากันไม่ได้กับนิพจน์ทั่วไปที่ใช้เชลล์ในรูปแบบอื่น

สิ่งเหล่านี้ใช้ไม่ได้:

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


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

เอาต์พุตโดยลบอักขระ "d" ทั้งหมดตามตัวอักษร

ho02123ware38384you44334o3434ingto38384ay

แต่สิ่งต่อไปนี้ทำงานได้ตามปกติ

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

เอาท์พุต

howareyoudoingtodday

หวังว่าจะทำให้สิ่งต่าง ๆ กระจ่างยิ่งขึ้น แต่ถ้าคุณยังไม่สับสนทำไมคุณไม่ลองบน Mac OS X ซึ่งเปิดใช้งานสถานะ REG_ENHANCED:

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

ในรสชาติส่วนใหญ่ของ * ระวังคุณจะเห็นผลลัพธ์ต่อไปนี้เท่านั้น:

d
d
d

Njoy!


6
ให้อภัย? ${foo//$bar/$baz}เป็นไม่ POSIX.2 BRE หรือ ERE ไวยากรณ์ - () มัน fnmatch - รูปแบบรูปแบบที่ตรงกัน
Charles Duffy

8
... ดังนั้นในขณะที่${hello//[[:digit:]]/}ทำงานถ้าเราต้องการที่จะกรองออกเพียงตัวเลขนำหน้าด้วยตัวอักษรo, ${hello//o[[:digit:]]*}จะมีพฤติกรรมที่แตกต่างอย่างสิ้นเชิงกว่าหนึ่งคาดว่า (ตั้งแต่ในรูปแบบ fnmatch, *ไม้ขีดไฟทุกตัวอักษรมากกว่าการปรับเปลี่ยนรายการทันทีก่อนที่จะเป็น 0 หรือมากกว่า)
Charles Duffy

1
ดูpubs.opengroup.org/onlinepubs/9699919799/utilities/… (และทุกอย่างที่รวมไว้โดยการอ้างอิง) สำหรับข้อมูลจำเพาะแบบเต็มบน fnmatch
Charles Duffy

1
man bash: มีตัวดำเนินการไบนารีเพิ่มเติม = ~ พร้อมใช้งานโดยมีลำดับความสำคัญเท่ากับ == และ! = เมื่อมีการใช้งานสตริงทางด้านขวาของโอเปอเรเตอร์จะถูกพิจารณาว่าเป็นนิพจน์ปกติที่ขยายเพิ่มและจับคู่ตามนั้น (ดังใน regex (3))
nickl-

1
@aderchox คุณถูกต้องสำหรับตัวเลขที่คุณสามารถใช้[0-9]หรือ[[:digit:]]
nickl-

13

หากคุณทำการโทรซ้ำและเกี่ยวข้องกับประสิทธิภาพการทดสอบนี้แสดงให้เห็นว่าวิธีการทุบตีเร็วกว่าการตีอย่างแรงถึง 15 เท่าและน่าจะเป็นกระบวนการภายนอกอื่น ๆ

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
หากคุณสนใจวิธีลดส้อมให้ค้นหาคำว่าnewConnectorในคำตอบนี้เป็นวิธีการตั้งค่าตัวแปรเป็นผลลัพธ์ของคำสั่งใน Bash
F. Hauri

8

ใช้[[:digit:]](สังเกตเครื่องหมายวงเล็บคู่) เป็นรูปแบบ:

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

แค่อยากจะสรุปคำตอบ (โดยเฉพาะอย่างยิ่ง @ nickl-ของhttps://stackoverflow.com/a/22261334/2916086 )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.