วิธีการส่ง regex เมื่อค้นหาเส้นทางไดเรกทอรีในทุบตี?


14

ฉันได้เขียนสคริปต์ทุบตีขนาดเล็กเพื่อหาถ้าไดเรกทอรีชื่อanacondaหรือผู้ใช้ของฉันminiconda $HOMEแต่ไม่พบminiconda2ไดเรกทอรีในบ้านของฉัน

ฉันจะแก้ไขสิ่งนี้ได้อย่างไร

if [ -d "$HOME"/"(ana|mini)conda[0-9]?" ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

PS: ถ้าฉันมี[ -d "$HOME"/miniconda2 ]; thenก็พบไดเรกทอรี miniconda2 ดังนั้นฉันคิดว่าข้อผิดพลาดอยู่ในส่วน"(ana|mini)conda[0-9]?"

ฉันต้องการให้สคริปต์เป็นเรื่องทั่วไป สำหรับฉันมันเป็น miniconda2 แต่สำหรับผู้ใช้อื่น ๆ มันอาจจะเป็น anaconda2, miniconda3 เป็นต้น


ผู้ใช้รายอื่นอาจใช้ anaconda_2 หรือ -2 หรือ -may2019 ดังนั้น xxxconda จะไม่ดีกว่าเหรอ?
WinEunuuchs2Unix

2
การขยายชื่อไฟล์ Bash ใช้การแสดงออกของ glob ไม่ใช่ regexes
ปีเตอร์

คำตอบ:


13

นี่เป็นสิ่งที่ยุ่งยากอย่างน่าประหลาดใจที่ต้องทำ

โดยพื้นฐานแล้ว-dจะทดสอบเพียงอาร์กิวเมนต์เดียวเท่านั้นแม้ว่าคุณจะสามารถจับคู่ชื่อไฟล์โดยใช้นิพจน์ทั่วไป

วิธีหนึ่งคือพลิกปัญหาและทดสอบไดเรกทอรีสำหรับการจับคู่ regex แทนที่จะทดสอบการจับคู่ regex สำหรับไดเรกทอรี กล่าวอีกนัยหนึ่งให้วนรอบไดเรกทอรีทั้งหมดในการ$HOMEใช้ shell glob ง่ายๆและทดสอบแต่ละรายการกับ regex ของคุณทำลายการแข่งขันและสุดท้ายทดสอบว่าBASH_REMATCHอาร์เรย์ไม่ว่างเปล่าหรือไม่:

#!/bin/bash

for d in "$HOME"/*/; do
  if [[ $d =~ (ana|mini)conda[0-9]? ]]; then
    break;
  fi
done

if ((${#BASH_REMATCH[@]} > 0)); then
    echo "anaconda/miniconda directory is found in your $HOME"
  else
    echo "anaconda/miniconda is not found in your $HOME"
fi

อีกวิธีหนึ่งคือการใช้เชลล์ทรงกลมแบบขยายแทน Regex และจับคู่แบบกลมใด ๆ ในอาร์เรย์ จากนั้นทดสอบว่าอาร์เรย์ไม่ว่างหรือไม่:

#!/bin/bash

shopt -s extglob nullglob

dirs=( "$HOME"/@(ana|mini)conda?([0-9])/ )

if (( ${#dirs[@]} > 0 )); then
  echo "anaconda/miniconda directory is found in your $HOME"
else
  echo "anaconda/miniconda is not found in your $HOME"
fi

การติดตาม/ทำให้แน่ใจว่ามีการจับคู่ไดเรกทอรีเท่านั้น nullglobป้องกันไม่ให้เปลือกจากการกลับสตริงที่ไม่ตรงกันในกรณีของการเป็นศูนย์การแข่งขัน


ในการสร้างแบบเรียกซ้ำให้ตั้งค่าglobstarตัวเลือกเชลล์ ( shopt -s globstar) จากนั้นตามลำดับ: -

  • (รุ่น regex): for d in "$HOME"/**/; do

  • (เวอร์ชัน glob แบบขยาย): dirs=( "$HOME"/**/@(ana|mini)conda?([0-9])/ )


1
ฉันไปตามเส้นทางอาเรย์ คุณสามารถใช้?([0-9])แทนที่@(|[0-9])- ?(...)จับคู่กับศูนย์หรือหนึ่งเดียวกับตัว?ระบุปริมาณregex
เกล็นแจ็

2
คุณไม่จำเป็นต้องใช้ extglob ด้วยซ้ำนั่นคือคุณใช้ส่วนขยายปีกกา (ซึ่งจะสร้างชื่อการจับคู่ที่เป็นไปได้ทั้งหมด):~/{ana,mini}conda{0..9}*/
xenoid

อย่างไรก็ตามมีการแก้ไขอย่างใดอย่างหนึ่งของการแก้ปัญหาเหล่านี้เพื่อที่จะถือแม้ถ้าminiหรือanacondaติดตั้งใน$HOME/sub-directories? ตัวอย่างเช่น$HOME/sub-dir1/sub-dir2/miniconda2
Jenny

1
@ เจนนี่โปรดดูการแก้ไขของฉันที่เกี่ยวข้องglobstar
steeldriver

1
@terdon ใช่ฉันไม่ต้องการลงไปในโพรงกระต่ายของสิ่งที่ "ถูกต้อง" เพื่อให้ตรงกับ - ฉันเพิ่งใช้ regex ของ OP ตาม - เพื่อจุดประสงค์ในการอธิบายวิธีการทั่วไป
steeldriver

9

อันที่จริงแล้วที่ได้กล่าวมานี้เป็นเรื่องยุ่งยาก แนวทางของฉันมีดังต่อไปนี้:

  • ใช้findและความสามารถของregexเพื่อค้นหาไดเรกทอรีที่เป็นปัญหา
  • ให้findพิมพ์xสำหรับแต่ละไดเรกทอรีที่พบ
  • เก็บxes ในสตริง
  • หากสตริงไม่ว่างเปล่าจะพบหนึ่งในไดเรกทอรี

ดังนั้น:

xString=$(find $HOME -maxdepth 1 \
                     -type d \
                     -regextype egrep \
                     -regex "$HOME/(ana|mini)conda[0-9]?" \
                     -printf 'x');
if [ -n "$xString" ]; then
    echo "found one of the directories";
else
    echo "no match.";
fi

คำอธิบาย:

  • find $HOME -maxdepth 1ค้นหาทุกสิ่งที่ด้านล่าง$HOME แต่ จำกัด การค้นหาไว้ที่ระดับหนึ่ง (นั่นคือ: มันจะไม่เก็บในไดเรกทอรีย่อย)
  • -type dจำกัด การค้นหาเฉพาะdไดเรกทอรีเท่านั้น
  • -regextype egrepบอกfindประเภทของการแสดงออกปกติที่เราจัดการกับ สิ่งนี้เป็นสิ่งจำเป็นเพราะสิ่งที่ชอบ[0-9]?และ(…|…)ค่อนข้างพิเศษและfind ไม่รู้จักพวกเขาโดยค่าเริ่มต้น
  • -regex "$HOME/(ana|mini)conda[0-9]?"เป็นนิพจน์ทั่วไปที่แท้จริงที่ เราต้องการมองหา
  • -printf 'x'เพียงพิมพ์xสำหรับทุกสิ่ง ที่ตรงตามเงื่อนไขก่อนหน้า

เมื่อมีการแข่งขัน -bash: -regex: command not found found one of the directories
เจนนี่

สวัสดี PerlDuck: ขอบคุณ คำตอบที่ดีเช่นกัน แต่ฉันได้รับข้อผิดพลาดprintfตัวอย่างเช่นเมื่อฉันรันสคริปต์มันก็โอเค แต่ไม่พบคำสั่ง printf เมื่อไม่มีการจับคู่ แต่ฉันคิดว่าเป็นเพราะไม่มีอะไรที่จะพิมพ์ -bash: -printf: command not found no match.
เจนนี่

3
@ เจนนี่คุณอาจพิมพ์ผิดเมื่อคัดลอกเพราะมันใช้งานได้ดีสำหรับฉัน -printfไม่ได้เป็นคำสั่ง findแต่อาร์กิวเมนต์ นั่นคือสิ่งที่แบ็กสแลชที่ส่วนท้ายของบรรทัดก่อนหน้าทำ
wjandrea

1
ฉันขอแนะนำ-quitหลังจากพิมพ์เส้นทางที่พบเว้นแต่คุณต้องการตรวจสอบความคลุมเครือ
ปีเตอร์

และทำไมไม่พิมพ์เส้นทางที่แท้จริง? คุณมีมันอยู่แล้วดังนั้นจึงเป็นเรื่องน่าละอายที่จะทิ้งมันและใช้xแทน:foundDir=$(find $HOME -maxdepth 1 -type d -regextype egrep -regex "$HOME/(ana|mini)conda[0-9]?" -print -quit); echo "found $foundDir"
terdon

2

คุณสามารถวนซ้ำรายชื่อไดเรกทอรีที่คุณต้องการทดสอบและดำเนินการกับมันหากมีอยู่ในรายการ:

a=0
for i in {ana,mini}conda{,2}; do
  if [ -d "$i" ]; then
    unset a
    break
  fi
done
echo "anaconda/miniconda directory is ${a+not }found in your $HOME"

วิธีนี้เห็นได้ชัดว่าไม่อนุญาตให้ใช้พลังงาน regex เต็มรูปแบบ แต่การขยายตัวของเปลือกและการรั้งรั้งนั้นอย่างน้อยก็ในกรณีที่คุณแสดง ห่วงออกทันทีที่หนึ่งไดเรกทอรีที่มีอยู่และ unsets aตัวแปรชุดก่อนหน้านี้ ในechoบรรทัดถัดมาการขยายพารามิเตอร์ ${a+not }จะขยายเป็นไม่มีอะไรหากaตั้งค่า (= ไม่พบ dir) และ“ ไม่”


1

สิ่งที่อาจเป็นไปได้คือการค้นหา miniconda และ anaconda แยกกันดังที่แสดงด้านล่าง

if [ -d "$HOME"/miniconda* ] || [ -d "$HOME"/anaconda* ]; then
    echo "miniconda directory is found in your $HOME"
else
    echo "anaconda/miniconda is not found in your $HOME"
fi

แต่ถ้ามีคนมีคำแนะนำฉันอยากจะรู้ว่าทำไมเราไม่สามารถส่ง regex เมื่อค้นหาไดเรกทอรี


2
ฉัน upvoting นี้ - แต่แล้วก็รู้ว่ามันจะพังถ้าผู้ใช้มีมากกว่าหนึ่งไดเรกทอรีที่ตรงกัน (เช่น miniconda และ miniconda2)
steeldriver

@steeldriver: "มันจะพังถ้าผู้ใช้มีไดเรกทอรีที่ตรงกันมากกว่าหนึ่งไดเรกทอรี" ใช่นั่นเป็นความจริงอย่างแน่นอน คุณมีข้อเสนอแนะวิธีการแก้ไขหรือไม่
เจนนี่

@Jenny ใช้อาร์เรย์เหมือนในคำตอบของ steeldriver shopt -s nullglob; dirs=( "$HOME"/miniconda* "$HOME"/anaconda* ); if (( ${#dirs[@]} > 0 )); then ...
wjandrea

หากคุณแทนที่] || [ด้วย-oอย่างน้อยไม่ควรแตกถ้าพบทั้งสองไดเร็กทอรีเนื่องจากไดเร็กทอรี globs ทั้งสองถูกค้นหาในการทดสอบเดียวกัน
Phoenix

@steeldriver และ Jenny: คุณอาจต้องการให้มันคลุมเครือแทนที่จะเลือกมัน กำหนดให้ผู้ใช้ระบุไดเรกทอรีแทนที่จะเลือกผิด (เช่นแก้ไขสคริปต์เพื่อตั้งชื่อ dir แทนที่จะเรียกใช้รหัสตรวจจับอัตโนมัติ)
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.