ฉันจะได้แมตช์แรกจากการขยายไวด์การ์ดได้อย่างไร


36

เชลล์เช่น Bash และ Zsh ขยาย wildcard เป็นอาร์กิวเมนต์โดยมีอาร์กิวเมนต์มากที่สุดเท่าที่ตรงกับรูปแบบ:

$ echo *.txt
1.txt 2.txt 3.txt

แต่ถ้าฉันต้องการให้การแข่งขันนัดแรกกลับมาไม่ใช่การแข่งขันทั้งหมด?

$ echo *.txt
1.txt

ฉันไม่สนใจวิธีแก้ปัญหาเฉพาะเชลล์ แต่ฉันต้องการโซลูชันที่ทำงานร่วมกับช่องว่างในชื่อไฟล์


ls * .txt | หัว -1
Archemar

1
@Archemar: ไม่ทำงานกับการขึ้นบรรทัดใหม่ในชื่อไฟล์
Flimm

คำตอบ:


24

วิธีหนึ่งที่แข็งแกร่งในการทุบตีคือการขยายเข้าไปในอาร์เรย์และส่งออกองค์ประกอบแรกเท่านั้น:

pattern="*.txt"
files=( $pattern )
echo "${files[0]}"  # printf is safer!

(คุณสามารถทำได้เพียงแค่echo $filesดัชนีที่ขาดหายไปนั้นถือเป็น [0])

สิ่งนี้จะจัดการกับพื้นที่ / แท็บ / บรรทัดใหม่และ metacharacters อื่น ๆ อย่างปลอดภัยเมื่อขยายชื่อไฟล์ โปรดทราบว่าการตั้งค่าภาษาจะมีผลต่อการเปลี่ยนแปลง "ครั้งแรก"

นอกจากนี้คุณยังสามารถทำเช่นนี้โต้ตอบกับทุบตีฟังก์ชั่นเสร็จสิ้น :

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}   # string to expand

    if compgen -G "$cur*" > /dev/null; then
        local files=( ${cur:+$cur*} )   # don't expand empty input as *
        [ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
    fi
}
complete -o bashdefault -F _echo echo

_echoฟังก์ชันนี้จะผูกฟังก์ชันเพื่อทำให้อาร์กิวเมนต์สมบูรณ์กับechoคำสั่ง (แทนที่การเสร็จสิ้นตามปกติ) มีการเพิ่ม "*" เพิ่มเติมในรหัสด้านบนคุณสามารถกดแท็บชื่อไฟล์บางส่วนและหวังว่าสิ่งที่ถูกต้องจะเกิดขึ้น

รหัสมีความซับซ้อนเล็กน้อยแทนที่จะตั้งค่าหรือสมมติว่าnullglob( shopt -s nullglob) เราตรวจสอบว่าcompgen -Gสามารถขยาย glob ไปสู่การแข่งขันบางส่วนจากนั้นเราขยายอย่างปลอดภัยไปยังอาร์เรย์และสุดท้ายตั้งค่า COMPREPLY เพื่อให้การอ้างอิงมีความแข็งแกร่ง

คุณสามารถทำสิ่งนี้ได้บางส่วน (โดยการขยายโปรแกรมแบบกลม) ด้วย bash's compgen -Gแต่ก็ไม่ได้ทนทานเพราะมันส่งผลโดยไม่ต้องพูดถึง stdout

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

คุณสามารถใช้compgenนอกฟังก์ชั่นที่เสร็จสมบูรณ์:

files=( $(compgen -W "$pattern") )

สิ่งหนึ่งที่ควรทราบคือ "~" ไม่ใช่รูปโค้ง แต่ถูกจัดการโดยการทุบตีในขั้นตอนการขยายแยกต่างหากเช่นเดียวกับตัวแปร $ และการขยายอื่น ๆ compgen -Gเพียงแค่ชื่อไฟล์กลมกลืน แต่compgen -Wให้การขยายเริ่มต้นทั้งหมดของ bash แม้ว่าอาจมีการขยายมากเกินไป (รวมถึง``และ $()) ซึ่งแตกต่างจาก-Gที่-W ถูกยกมาได้อย่างปลอดภัย (ฉันไม่สามารถอธิบายความแตกต่าง) เนื่องจากวัตถุประสงค์ของ-Wมันคือมันขยายโทเค็นซึ่งหมายความว่ามันจะขยาย "a" ถึง "a" แม้ว่าจะไม่มีไฟล์ดังกล่าวอยู่ดังนั้นจึงอาจไม่เหมาะ

สิ่งนี้ง่ายต่อการเข้าใจ แต่อาจมีผลข้างเคียงที่ไม่พึงประสงค์:

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local files=( $(compgen -W "$cur") ) 
    printf -v COMPREPLY %q "${files[0]}"  
}

แล้ว:

touch $'curious \n filename'

echo curious*tab

หมายเหตุการใช้printf %qเพื่ออ้างอิงค่าอย่างปลอดภัย

ตัวเลือกสุดท้ายคือการใช้เอาต์พุตที่คั่นด้วย 0 กับยูทิลิตี้ GNU (ดูคำถามที่พบบ่อยทุบตี ):

pattern="*.txt"
while IFS= read -r -d $'\0' filename; do 
    printf '%q' "$filename"; 
    break; 
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )

ตัวเลือกนี้ช่วยให้คุณสามารถควบคุมคำสั่งการเรียงลำดับได้มากขึ้น (คำสั่งเมื่อขยาย glob จะขึ้นอยู่กับสถานที่ของคุณ / LC_COLLATEและอาจจะใช่หรือไม่ก็ได้) แต่เป็นค้อนที่ค่อนข้างใหญ่สำหรับปัญหาเล็ก ๆ ;-)


20

ใน zsh ใช้คัดเลือก[1] glob โปรดทราบว่าแม้ว่ากรณีพิเศษนี้จะส่งกลับได้มากที่สุดหนึ่งรายการ แต่ยังคงเป็นรายการและ globs จะไม่ขยายในบริบทที่คาดว่าจะมีคำเดียวเช่นการมอบหมาย (การมอบหมายอาร์เรย์)

echo *.txt([1])

ใน ksh หรือ bash คุณสามารถคัดรายการทั้งหมดของการแข่งขันในอาร์เรย์และใช้องค์ประกอบแรก

tmp=(*.txt)
echo "${tmp[0]}"

ในเชลล์ใด ๆ คุณสามารถตั้งค่าพารามิเตอร์ตำแหน่งและใช้พารามิเตอร์แรกได้

set -- *.txt
echo "$1"

สิ่งนี้ปิดบังพารามิเตอร์ตำแหน่ง หากคุณไม่ต้องการสิ่งนั้นคุณสามารถใช้ subshell

echo "$(set -- *.txt; echo "$1")"

คุณยังสามารถใช้ฟังก์ชันซึ่งมีชุดพารามิเตอร์ตำแหน่งของตัวเอง

set_to_first () {
  eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"

1
และเพื่อรับการแข่งขัน $ n $ ครั้งแรกคุณสามารถใช้*.txt([1,n])
เอ็มเร

6

ลอง:

for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt

โปรดทราบว่าการขยายชื่อไฟล์จะถูกจัดเรียงตามลำดับการเรียงที่มีผลบังคับใช้ในโลแคลปัจจุบัน



1

ฉันเพิ่งพบคำถามเก่านี้ในขณะที่สงสัยในสิ่งเดียวกัน ฉันลงเอยด้วยสิ่งนี้:

echo $(ls *.txt | head -n1)

แน่นอนคุณสามารถแทนที่headด้วยtailและ-n1ด้วยหมายเลขอื่น ๆ


ข้างต้นจะไม่ทำงานหากคุณทำงานกับไฟล์ที่มีการขึ้นบรรทัดใหม่ในชื่อของพวกเขา ในการทำงานกับ newlines คุณสามารถใช้สิ่งเหล่านี้:

  • ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g' (ใช้งานไม่ได้กับ BSD)
  • ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
  • ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
  • echo -e "$(ls -b *.txt | head -n1)" (ใช้ได้กับอักขระพิเศษใด ๆ )

3
ไม่นั่นจะล้มเหลวหากชื่อไฟล์มีการขึ้นบรรทัดใหม่
ไอแซก

6
โลกแห่งความบ้าคลั่งแบบไหนที่เราอาศัยอยู่ในที่ที่ชื่อไฟล์มีการขึ้นบรรทัดใหม่
billynoah

-1

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

คำสั่งนี้มักจะทำเพื่อฉัน:

export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)

คำเตือน: การขยายตัว Glob จะไม่เรียงลำดับโฟลเดอร์ของคุณตาม semver คุณได้รับการเตือน นี่เป็นสิ่งที่ยอดเยี่ยมหากคุณมีDockerfileไดเรกทอรีเพียงเวอร์ชันเดียว แต่รุ่นไดเรกทอรีนั้นอาจแตกต่างกันไปในแต่ละภาพ image


ยินดีต้อนรับสู่ U&L! สิ่งนี้จัดการกับชื่อไดเรกทอรีส่วนใหญ่ แต่ไม่จัดการชื่อไดเรกทอรีด้วยการขึ้นบรรทัดใหม่ ลองสร้างไดเรกทอรีเช่นนี้mkdir "$(echo one; echo two)"และดูว่าฉันหมายถึงอะไร
Flimm

อะไรคือข้อได้เปรียบเมื่อเทียบกับทางเลือกอื่น ๆ โดยเฉพาะอย่างยิ่งรุ่นที่ใช้tail?
RalfFriedl

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

@Flimm จุดดี; ฉันคิดว่านักพัฒนาซอฟต์แวร์ส่วนใหญ่จะมีปัญหาที่ใหญ่กว่าในการจัดการกับถ้าโครงสร้างโฟลเดอร์ของพวกเขามีการขึ้นบรรทัดใหม่ในนั้น ... ฉันไม่เคยจัดการกับเรื่องนั้นและไม่คาดหวังว่าจะมีคอนเทนเนอร์และซอฟต์แวร์ที่ฉันใช้
Andrew Odri

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