รับรายการของไดเรกทอรีย่อยที่มีไฟล์ที่ชื่อมีสตริง


45

ฉันจะรับรายการไดเรกทอรีย่อยที่มีไฟล์ที่มีชื่อตรงกับรูปแบบเฉพาะได้อย่างไร

โดยเฉพาะอย่างยิ่งฉันกำลังมองหาไดเรกทอรีที่มีไฟล์ที่มีตัวอักษร 'f' บางที่เกิดขึ้นในชื่อไฟล์

ตามหลักการแล้วรายการจะไม่ซ้ำกันและมีเส้นทางเท่านั้นหากไม่มีชื่อไฟล์

คำตอบ:


43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

ด้านบนค้นหาไฟล์ทั้งหมดด้านล่างไดเรกทอรีปัจจุบัน ( .) ที่เป็นไฟล์ปกติ ( -type f) และมีfบางแห่งในชื่อ ( -name '*f*') จากนั้นsedลบชื่อไฟล์ออกจากชื่อไดเรกทอรี จากนั้นรายการไดเรกทอรีจะถูกจัดเรียง ( sort) และลบรายการที่ซ้ำกัน ( uniq)

sedคำสั่งประกอบด้วยตัวแทนที่เดียว ค้นหาการจับคู่กับนิพจน์ทั่วไป/[^/]+$และแทนที่การจับคู่ที่ไม่มีอะไร เครื่องหมายดอลลาร์หมายถึงจุดสิ้นสุดของบรรทัด [^/]+'หมายถึงอักขระหนึ่งตัวขึ้นไปที่ไม่ได้เป็นสแลช ดังนั้น/[^/]+$หมายถึงอักขระทั้งหมดจากเครื่องหมายทับสุดท้ายถึงจุดสิ้นสุดของบรรทัด กล่าวอีกนัยหนึ่งสิ่งนี้ตรงกับชื่อไฟล์ที่ส่วนท้ายของพา ธ เต็ม ดังนั้นคำสั่ง sed จะลบชื่อไฟล์โดยไม่เปลี่ยนแปลงชื่อของไดเร็กทอรีที่ไฟล์นั้นอยู่

simplifications

sortคำสั่งที่ทันสมัยจำนวนมากรองรับการ-uตั้งค่าสถานะซึ่งทำให้uniqไม่จำเป็น สำหรับ GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

และสำหรับ MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

นอกจากนี้หากfindคำสั่งของคุณรองรับอาจเป็นไปได้ที่จะfindพิมพ์ชื่อไดเรกทอรีโดยตรง สิ่งนี้หลีกเลี่ยงความต้องการsed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

รุ่นที่มีประสิทธิภาพมากขึ้น (ต้องใช้เครื่องมือ GNU)

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

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'

ฉันมีไฟล์จำนวนมากที่ทำให้การเรียงลำดับทั้งหมดมีราคาแพงเกินไป การขว้างปาuniqลงไปในส่วนผสมนั้นช่วยได้มากโดยการลบเส้นซ้ำที่อยู่ติดกันออกไป find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. หรือหากเครื่องมือของคุณเก่าไปหน่อย uniq อาจไม่มีตัวเลือก -z find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112

1
ผู้ใช้ MacOS: แฟล็ก sed ไม่ใช่ -r ด้วยเหตุผลบางอย่าง -E
David

@ David จริงมาก คำตอบได้รับการอัปเดตเพื่อแสดง-Eสำหรับ MacOS
John1024

22

ทำไมไม่ลองอันนี้:

find / -name '*f*' -printf "%h\n" | sort -u

คำตอบที่ดีที่สุด เข้ากันได้กับ POSIX อย่างสิ้นเชิงไม่เหมือนคำตอบข้างต้นข้างต้นและยังได้รับรางวัลThe Shortest Pipeline Prize :)
kkm

ฉันชอบที่จะเห็นใครบางคนแสดงช่วงเวลาของเรื่องนี้เทียบกับคนอื่น ๆ ข้างต้นเพราะฉันรู้สึกว่านี่เป็นเรื่องที่เร็วที่สุด
dlamblin

4
@kkm ฉันเห็นด้วยว่านี่เป็นทางออกที่ดีที่สุด แต่POSIXfind spec forนั้นค่อนข้างกระจัดกระจาย - ไม่ได้ระบุ-printfโอเปอเรเตอร์ นี้ไม่ได้ทำงานกับ BSD ดังนั้นไม่ใช่ "POSIX ทั้งหมดที่เข้ากันได้" (แม้ว่าจะอยู่ใน POSIX )findsort -u
Wildcard

8

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

ตัวอย่างข้อมูล

สำหรับตัวอย่างด้านล่างเราจะใช้ข้อมูลต่อไปนี้

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

ลบ*f*ไฟล์บางส่วนออกจากdir1/*:

$ rm dir1/dir10{0..2}/*f*

วิธีการ # 1 - แยกวิเคราะห์ผ่านสตริง

นี่เรากำลังจะใช้เครื่องมือดังต่อไปนี้find, และgrepsort

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

วิธีการ # 2 - แยกวิเคราะห์โดยใช้ไฟล์

โซ่เครื่องมือเช่นเดียวกับก่อนยกเว้นในครั้งนี้เราจะใช้แทนdirnamegrep

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

หมายเหตุ:ตัวอย่างข้างต้นใช้head -5เพื่อ จำกัด จำนวนเอาต์พุตที่เราจัดการกับตัวอย่างเหล่านี้เท่านั้น โดยปกติแล้วพวกเขาจะถูกลบออกเพื่อรับรายชื่อของคุณ!

การเปรียบเทียบผลลัพธ์

เราสามารถใช้timeเพื่อดู 2 วิธี

dirname

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

ดังนั้นจึงเป็นการดีที่สุดที่จะจัดการกับสตริงหากเป็นไปได้

วิธีการแยกสตริงทางเลือก

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u

+1 เพราะใช้งานได้ แต่น่าสนใจนี่ใช้เวลานานกว่าคำตอบของ @ John1024
Muhd

@Muhd - ใช่การเรียกไปยัง dirname นั้นช้า ฉันกำลังหาทางเลือก
slm


1

คำตอบนี้ขึ้นอยู่กับคำตอบ slm อย่างไร้ยางอาย มันเป็นวิธีการที่น่าสนใจ แต่มีข้อ จำกัด หากชื่อไฟล์และ / หรือไดเรกทอรีมีตัวอักษรพิเศษ (ช่องว่างกึ่งคอลัมน์ ... ) find /somewhere -print0 | xargs -0 someprogamนิสัยที่ดีคือการใช้งาน

ตัวอย่างข้อมูล

สำหรับตัวอย่างด้านล่างเราจะใช้ข้อมูลต่อไปนี้

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

ลบ*f*ไฟล์บางส่วนออกจากdir1/*/:

rm dir1/dir\ 10{0..2}/*f*

วิธีการ # 1 - แยกวิเคราะห์โดยใช้ไฟล์

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

หมายเหตุ : ตัวอย่างข้างต้นใช้head -5เพื่อ จำกัด จำนวนเอาต์พุตที่เราจัดการกับตัวอย่างเหล่านี้เท่านั้น โดยปกติแล้วพวกเขาจะถูกลบออกเพื่อรับรายชื่อของคุณ! นอกจากนี้แทนที่echoคำสั่งสิ่งที่คุณต้องการใช้


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