การจับคู่รูปแบบกับชื่อพา ธ ใน bash


10

ฉันต้องการดำเนินการกับรายการไดเรกทอรีย่อยในไดเรกทอรี พิจารณา:

for x in x86-headers/*/C/populate.sh; do echo $x; done

สิ่งนี้จะช่วยให้

x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh

แต่ฉันต้องการค่าที่สอดคล้องกับส่วนที่สองของเส้นทาง elf, glฯลฯ x86-headersฉันรู้วิธีการเปลื้องผ้าชั้นนำ

for x in x86-headers/*/C/populate.sh; do i=${x##x86-headers/}; echo $i; done

ซึ่งจะช่วยให้

elf/C/populate.sh
gl/C/populate.sh
gmp/C/populate.sh
gnome2/C/populate.sh
gtk2/C/populate.sh
jni/C/populate.sh
libc/C/populate.sh

ฉันรู้วิธีถอดคำศัพท์ในภายหลังในเส้นทางด้วย เช่นลงไปหนึ่งระดับ:

cd x86-headers
for x in */C/populate.sh; do i=${x%%/*}; echo $i; done

จะช่วยให้

elf
gl
gmp
gnome2
gtk2
jni
libc

แต่การพยายามรวมสิ่งเหล่านี้ไม่ได้ผล กล่าวคือ

for x in x86-headers/*/C/populate.sh; do i=${${x##x86-headers}%%/*}; echo $i; done

จะช่วยให้

bash: ${${x##x86-headers}%%/*}: bad substitution

ไม่สงสัยเลยว่านี่เป็นไวยากรณ์ที่ไม่ถูกต้อง แต่ฉันไม่รู้จักไวยากรณ์ที่ถูกต้อง แน่นอนว่าอาจมีวิธีที่ดีกว่าในการทำเช่นนี้ ถ้าฉันใช้ Python ฉันจะใช้ split on /เพื่อแยกแต่ละเส้นทางลงในรายการแล้วเลือกองค์ประกอบที่สอง แต่ฉันไม่รู้วิธีการทำเช่นนั้นในการทุบตี

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

คำตอบ:


9

คุณไม่สามารถรวมเข้ากับbash(หรือ POSIXly) คุณต้องทำสองขั้นตอน

i=${x#*/}; i=${i%%/*}

สามารถพกพาไปยัง POSIX เชลล์ได้ หากคุณต้องการความสะดวกในการพกพาไปที่ Bourne shell (แต่ทำไมคุณต้องติดแท็กคำถาม/ bashของคุณ?) เช่นกันในกรณีที่คุณย้ายไปที่ Solaris และถูกบังคับให้ใช้/bin/shแทนมาตรฐานที่shนั่นหรือระบบเก่าอายุ 20 ปี) คุณสามารถใช้วิธีนี้ (ซึ่งจะทำงานได้ดีกับ POSIX เชลล์):

IFS=/; set -f
set x $x
i=$3

(ด้านบนเป็นหนึ่งในกรณีที่หายากมากซึ่งมันสมเหตุสมผลดีที่จะปล่อยตัวแปรไว้โดยไม่มีการอ้างอิง)

เพียงเพื่อบันทึกด้วย zsh:

print -rl -- x86-headers/*/C/populate.sh(:h:h:t)

( t ail ของh ead ของh ead ของไฟล์)

หรือสำหรับpythonแนวทางของคุณ:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}

เครื่องหมายอัฒภาคi=${x#*/}; i=${i%%/*}เป็นตัวเลือกใช่ไหม
Dimitre Radoulov

3
@DimitreRadoulov เป็นตัวเลือก แต่เชลล์บางตัวเช่น/ เวอร์ชันเก่าบางรุ่นashdashจะรันการมอบหมายจากขวาไปซ้าย (ซึ่งเป็นวิธีที่เชลล์เป้าหมายทำงาน) และทำให้เกิดปัญหาในกรณีนี้
Stéphane Chazelas

7

การขยายพารามิเตอร์ไม่อนุญาตให้คุณซ้อนการแสดงออกดังนั้นคุณจำเป็นต้องทำสองขั้นตอนด้วยการกำหนด:

i=${x##x86-headers/}
i=${i%%/*}

คุณมีตัวเลือกในการใช้อาร์เรย์ที่นี่ แต่ฉันคิดว่ามันยุ่งยากกว่า PE ข้างต้น:

IFS=/
set -f # Disable globbing in case unquoted $x has globs in it
i=( $x )
set +f
echo "${i[1]}"

จากนั้นมีตัวเลือกที่สามของการใช้คำสั่งภายนอก ด้วยรายการสั้น ๆ ของไฟล์นี่เป็นตัวเลือกที่มีประสิทธิภาพน้อยที่สุด แต่จะขยายขนาดให้ดีที่สุดเมื่อจำนวนไฟล์เพิ่มขึ้น:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3

นั่นไม่ใช่ความจริงของกระสุนทั้งหมด แต่zshอนุญาตให้ทำรังได้
Stéphane Chazelas

3
@StephaneChazelas ยุติธรรมพอ bashแต่คำถามนี้ถูกแท็ก
kojiro

2
regex="x86-headers/([^/]*)/.*"
for f in x86-headers/*/C/populate.sh; do [[ $f =~ $regex ]] && echo "${BASH_REMATCH[1]}"; done
elf
gl
gmp
gnome2
gtk2
jni
libc

2

ใช้ความระมัดระวังในการสร้างเป็นร้อง:

# What happen if no files match ?
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

echo *ขณะที่คุณจะสามารถสิ้นสุดการทำ ในกรณีนี้ก็เป็นเพียงตลก แต่มันอาจจะเลวร้ายที่สุดมากถ้าคุณเป็นรากของคุณCWDมี/และคุณต้องการที่จะลบไดเรกทอรีย่อยบางส่วนของ/tmpแทนการสะท้อนพวกเขา!

ในการแก้ไขปัญหาด้วยการทุบตีคุณสามารถทำได้:

shopt -s nullglob
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done
shopt -u nullglob

แจ้งให้ทราบshopt -u nullglobในตอนท้ายเพื่อคืนค่าพฤติกรรมทุบตีเริ่มต้นหลังจากวง

โซลูชันแบบพกพาอื่น ๆ อาจเป็น:

for x in x86-headers/*/C/populate.sh; do
  [ -e $x ] || break
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

(การแทนที่##และ%%พารามิเตอร์ถูกต้องในksh88 )

ตามลิงค์นี้สำหรับความสมบูรณ์มากขึ้นการอภิปรายของ nullglob

โปรดสังเกตว่าโซลูชัน 3 ข้อด้านบนนั้นล้มเหลวหากคุณมีไดเรกทอรีตัวกลางที่มีช่องว่างในชื่อ หากต้องการแก้ปัญหาให้ใช้เครื่องหมายคำพูดคู่ในการทดแทนตัวแปร:

for x in x86-headers/*/C/populate.sh; do
  [ -e "$x" ] || break
  x="${x##x86-headers/}"
  x="${x%%/*}"
  echo "$x"
done

0

หากต้องการจับคู่รูปแบบกับชื่อพา ธ คุณอาจตั้งค่าIFSตัวแปรเชลล์เป็น/แล้วใช้การขยายพารามิเตอร์เชลล์

การทำทั้งหมดนี้ใน subshell ช่วยให้สภาพแวดล้อมของ parent shell ไม่เปลี่ยนแปลง!

# cf. "The real Bourne shell problem",
# http://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellLists

paths='
x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh
'

IFS='
'

# version 1
for x in $paths; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done


# version 2
set -- ${paths}
for x in $@; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.