Grep สำหรับรูปแบบที่เริ่มต้นหรือกลางบรรทัด


9

ฉันจะเริ่มต้นด้วยการบอกว่าฉันคิดว่าปัญหานี้เป็นผู้บริสุทธิ์น้อยกว่าเล็กน้อยฟังดู

สิ่งที่ฉันต้องทำ: ตรวจสอบโฟลเดอร์ภายในตัวแปรสภาพแวดล้อม PATH อาจเป็นตอนเริ่มต้นหรือหลังจากนั้น ฉันแค่ต้องยืนยันว่าโฟลเดอร์นั้นอยู่ที่นั่น

ตัวอย่างปัญหาของฉัน - มาใช้/opt/gnomeกัน


สถานการณ์ที่ 1: โฟลเดอร์ไม่ได้อยู่ที่ตำแหน่งเริ่มต้นของ PATH

# echo "$PATH"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

# echo "$PATH" | grep ":/opt/gnome"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

โปรดทราบว่าความต้องการ grep /var/opt/gnomeจะเฉพาะเจาะจงมากพอเพื่อที่จะไม่จับ ดังนั้นลำไส้ใหญ่


สถานการณ์ที่ 2: โฟลเดอร์อยู่ที่จุดเริ่มต้นของ PATH

# echo "$PATH"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

# echo "$PATH" | grep "^/opt/gnome"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

นี่คือปัญหาของฉัน - ฉันต้องค้นหาโคลอนหรือเริ่มต้นของบรรทัดด้วยโฟลเดอร์นี้ สิ่งที่ฉันต้องการทำคือหนึ่งในสองนิพจน์วงเล็บเหลี่ยม:

# echo $PATH | grep "[^:]/opt/gnome"
# echo $PATH | grep "[:^]/opt/gnome"

แต่[^และ[:มีความหมายของตัวเอง ดังนั้นทั้งสองคำสั่งข้างต้นจึงไม่ทำงาน

มีวิธีที่ฉันสามารถ grep สำหรับสองสถานการณ์ในหนึ่งคำสั่ง?


โปรดทราบว่าความคิดเห็นของกิลส์กับคำตอบของ Costas นำไปใช้กับคำถามเกินไป: เนื่องจากคุณไม่ grepping สำหรับ/opt/gnome:หรือ/opt/gnome$คุณจะพบหรือ/opt/gnome-foo /opt/gnome/bar
สกอตต์

@Scott - ตราบใดที่คุณรวมไว้ในการจับคู่ของคุณในพื้นที่ที่มีการแทรกแซงคุณสามารถยึดสายใด ๆ ที่หัวและหางของบรรทัดโดยไม่มีภาวะแทรกซ้อนดังกล่าว เช่นเดียวกับgrep '^\(any number of other matches:*:\)*my match\(:.*\)*$'
mikeserv

คำตอบ:


10

หากคุณกำลังตรวจสอบเนื้อหาของPATHตัวแปรสภาพแวดล้อมซึ่งต่างจากการค้นหาบางสิ่งในไฟล์แสดงว่าgrepเป็นเครื่องมือที่ผิด มันง่ายกว่า (และเร็วกว่าและอ่านได้ง่ายกว่า) ในเชลล์

ใน bash, ksh และ zsh:

if [[ :$PATH: = *:/opt/gnome:* ]]; then
 : # already there
else
  PATH=$PATH:/opt/gnome
fi

portably:

case :$PATH: in
  *:/opt/gnome:*) :;; # already there
  *) PATH=$PATH:/opt/gnome;;
esac

สังเกตการใช้งาน:$PATH:มากกว่า$PATH; $PATHวิธีนี้องค์ประกอบล้อมรอบเสมอโดยทวิภาคในสตริงการค้นหาแม้ว่ามันจะเป็นจุดเริ่มต้นหรือจุดสิ้นสุดของ

หากคุณกำลังค้นหาผ่านบรรทัดของไฟล์คุณสามารถใช้ regexp แบบขยาย (เช่นต้องมีgrep -E) (^|:)/opt/gnome($|:)เพื่อจับคู่/opt/gnomeแต่เฉพาะเมื่อเริ่มต้นบรรทัดหรือตามหลังเครื่องหมายโคลอนและต่อท้ายเครื่องหมายโคลอน บรรทัดหรือตามด้วยเครื่องหมายจุดคู่


8

คุณสามารถใช้นิพจน์ปกติแบบขยายได้โดยใช้เพียงแค่ grep -E

คุณต้องจับคู่จุดเริ่มต้นและจุดสิ้นสุดของเส้นทางที่คุณกำลังพยายามค้นหาถ้าคุณต้องการหลีกเลี่ยงการบวกเท็จ

จับคู่อินสแตนซ์ที่จุดเริ่มต้น:

$ TEST=/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome

ตรงกับตัวอย่างที่ตรงกลางด้วย:

$ TEST=/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"
/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome

หลีกเลี่ยงการบวกเท็จ:

$ TEST="/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta"
$ echo $TEST | grep -E "(:|^)/opt/gnome(:|$)"

ไม่มีการแข่งขัน

กะทัดรัดและสง่างาม ทดสอบกับ Debian 7


1
egrepคือการใช้เลิกใช้grep -E(ที่มา: man grep)
โธ

ขอบคุณทำงานเหมือนจับใจ! ฉันไม่ได้เลือกมันเป็นคำตอบเพราะฉันคิดว่าตัวเลือก -w ง่ายกว่านิดหน่อย เรียบง่ายกว่าที่ฉันจินตนาการไว้ในตอนแรก!
JamesL

3
คำเตือน. -wตัวเลือกที่มีปัญหาบางอย่าง เฉพาะตัวเลขตัวอักษรและเครื่องหมายขีดล่างเท่านั้นที่ถือว่าเป็น "คำ" ดังนั้นตัวอักษรที่ผิดปกติ แต่เป็นไปได้บางอย่างจะทำให้มันล้มเหลว ตัวอย่างและecho '/sbin:/usr/sbin:/var-/opt/gnome' | grep -w "/opt/gnome" echo '/sbin:/usr/sbin:/var./opt/gnome' | grep -w "/opt/gnome"สิ่งเหล่านั้นให้ผลลัพธ์ที่ผิด
Luis Antolín Cano

1
คุณกำลังติดตามถูกต้อง แต่ยังคงมีผลบวกที่ผิดพลาด: /opt/gnome/somethingelse.
Gilles 'หยุดความชั่วร้าย'

1
ถูกต้องทั้งหมด เราควรใส่ใจกับจุดจบอย่างชัดเจนไม่เพียง แต่การเริ่มต้น ฉันคิดว่ามันช่วยแก้ปัญหาecho "/home/bob/opt/gnome:/opt/gnome/somethingelse:/opt/gnome-beta" | grep -E "(:|^)/opt/gnome(:|$)"ได้ กำลังแก้ไขคำตอบ
Luis Antolín Cano

7

หากคุณไม่ได้แต่งงานgrepคุณสามารถใช้awkและแยกบันทึกได้:

awk 'BEGIN {RS=":"} /^\/opt\/gnome$/'

5

คุณสามารถใช้

echo "$PATH" | tr ':' '\n' | grep -x "/opt/gnome"

ซึ่งแยกตัวแปรพา ธ ออกเป็นบรรทัดที่แยกกัน (หนึ่งรายการต่อเส้นทาง) ดังนั้นgrep -xสามารถค้นหาผลลัพธ์ที่แน่นอนได้ trนี้มีแน่นอนข้อเสียที่จะต้องเป็นกระบวนการที่เพิ่มเติมสำหรับ และจะไม่ทำงานเมื่อชื่อโฟลเดอร์ในPATHประกอบด้วยอักขระขึ้นบรรทัดใหม่


2

ฉันไม่รู้ว่ามันเพียงพอสำหรับคำตอบ แต่

grep -w "/opt/gnome"

จะตอบสนองความต้องการของคุณ

echo '/sbin:/usr/sbin:/opt/gnome:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome
echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep -w "/opt/gnome" -o
/opt/gnome

แต่

echo '/opt/gnome:/sbin:/usr/sbin:/var/opt/gnome' | grep "/opt/gnome" -o
/opt/gnome
/opt/gnome

วิธีนี้ใช้งานได้ดีเพราะโคลอนเป็นตัวอักษร ขอบคุณ!
JamesL

@ Sman865 มีเหตุผลอื่น: เพราะ/ไม่ได้เป็นส่วนหนึ่งของคำ แต่rเป็น
Costas

2
คำเตือน. ตามที่ฉันพูดในความคิดเห็นในคำตอบของฉัน มีอักขระถูกกฎหมายสำหรับชื่อไดเรกทอรีที่ไม่ใช่ตัวอักษรคำ นั่นนำไปสู่ผลลัพธ์ที่ผิด ไม่ใช่เรื่องปกติที่จะสิ้นสุดชื่อไดเรกทอรีใน แต่อาจเกิดขึ้นได้
Luis Antolín Cano

4
@ Sman865 บวกเท็จ: /opt/gnome-beta,, /home/bob/opt/gnome
Gilles 'SO- หยุดความชั่วร้าย'

กรณีที่ไม่ทำงาน: grep -w /usr/local -o <<< /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games------/usr/local /usr/local /usr/local
pabouk

0

เพื่อเลือก/opt/gnomeล้อมรอบด้วยอักขระที่ไม่ใช่คำ (สายใหม่:, /ฯลฯ ) ลองนี้:

grep '\B/opt/gnome'

0

grepคุณสามารถทำเช่นนี้ได้อย่างน่าเชื่อถือและมีความพยายามน้อยใน คุณสามารถใช้ประโยชน์จากส่วนขยายที่พร้อมใช้งานอย่างกว้างขวางและจากวิธีการแก้ปัญหาจำนวนมากที่ได้รับการเสนอแล้ว แต่แม้จะมี regex ขั้นพื้นฐานก็ทำได้ง่ายแม้ว่ามันอาจจะไม่ได้สังหรณ์ใจในตอนแรก

ด้วย regex พื้นฐาน - ด้วยgrep- คุณมีสมอที่เชื่อถือได้สองอัน - หัวและหางของเส้น คุณสามารถจับคู่การแข่งขันกับทั้งคู่ได้โดยไม่ต้องคำนึงถึงตำแหน่งในบรรทัดเช่น:

grep '^\(ignore case, delimiter\)*match\(delimiter, ignore case\)*$'

grepจะจับคู่จากส่วนหัวของบรรทัดย่อยเป็นจำนวนมากของ\(grouped\)นิพจน์ย่อยตามที่จะต้องพบกับตัวคั่นถัดไปจากนั้นจึงจับคู่ที่ชัดเจนของคุณและจากหางของการจับคู่ของคุณไปยังส่วนท้ายของบรรทัดในลักษณะเดียวกัน หากการจับคู่ที่ชัดแจ้งของคุณไม่ตรงกันอย่างชัดเจนมันจะล้มเหลวและไม่พิมพ์อะไรเลย

ตัวอย่างเช่นคุณอาจทำ:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$'

ดูตัวเอง:

grep '^\(.*:\)*/opt/gnome\(:.*\)*$
' <<\INPUT
/opt/gnome-beta
/opt/gnome
/home/bob/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt-gnome-beta
/opt/gnomenot::::/opt/gnome
INPUT

เอาท์พุท

/opt/gnome
:/opt/gnome:
/home/bob/opt/gnome:/opt/gnome:/opt/gnome-beta
/opt/gnomenot::::/opt/gnome

0

คุณสังเกตเห็นกรณีขอบ ... คุณสามารถหลีกเลี่ยงได้โดยการบังคับให้การประจักษ์ของ: ที่จุดเริ่มต้นของบรรทัด:

 echo ":$PATH" | grep ":/opt/gnome"

หรือหากเส้นทางนั้นถูกต้องให้เพิ่มอีกหนึ่งเส้นทางที่จุดสิ้นสุดเพื่อให้แน่ใจว่ามีขอบเขต:

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