ฉันจะรับรายการไดเรกทอรีย่อยที่มีไฟล์ที่มีชื่อตรงกับรูปแบบเฉพาะได้อย่างไร
โดยเฉพาะอย่างยิ่งฉันกำลังมองหาไดเรกทอรีที่มีไฟล์ที่มีตัวอักษร 'f' บางที่เกิดขึ้นในชื่อไฟล์
ตามหลักการแล้วรายการจะไม่ซ้ำกันและมีเส้นทางเท่านั้นหากไม่มีชื่อไฟล์
ฉันจะรับรายการไดเรกทอรีย่อยที่มีไฟล์ที่มีชื่อตรงกับรูปแบบเฉพาะได้อย่างไร
โดยเฉพาะอย่างยิ่งฉันกำลังมองหาไดเรกทอรีที่มีไฟล์ที่มีตัวอักษร 'f' บางที่เกิดขึ้นในชื่อไฟล์
ตามหลักการแล้วรายการจะไม่ซ้ำกันและมีเส้นทางเท่านั้นหากไม่มีชื่อไฟล์
คำตอบ:
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq
ด้านบนค้นหาไฟล์ทั้งหมดด้านล่างไดเรกทอรีปัจจุบัน ( .
) ที่เป็นไฟล์ปกติ ( -type f
) และมีf
บางแห่งในชื่อ ( -name '*f*'
) จากนั้นsed
ลบชื่อไฟล์ออกจากชื่อไดเรกทอรี จากนั้นรายการไดเรกทอรีจะถูกจัดเรียง ( sort
) และลบรายการที่ซ้ำกัน ( uniq
)
sed
คำสั่งประกอบด้วยตัวแทนที่เดียว ค้นหาการจับคู่กับนิพจน์ทั่วไป/[^/]+$
และแทนที่การจับคู่ที่ไม่มีอะไร เครื่องหมายดอลลาร์หมายถึงจุดสิ้นสุดของบรรทัด [^/]+'
หมายถึงอักขระหนึ่งตัวขึ้นไปที่ไม่ได้เป็นสแลช ดังนั้น/[^/]+$
หมายถึงอักขระทั้งหมดจากเครื่องหมายทับสุดท้ายถึงจุดสิ้นสุดของบรรทัด กล่าวอีกนัยหนึ่งสิ่งนี้ตรงกับชื่อไฟล์ที่ส่วนท้ายของพา ธ เต็ม ดังนั้นคำสั่ง sed จะลบชื่อไฟล์โดยไม่เปลี่ยนแปลงชื่อของไดเร็กทอรีที่ไฟล์นั้นอยู่
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
เวอร์ชันด้านบนจะสับสนโดยชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่ โซลูชันที่มีประสิทธิภาพมากขึ้นคือทำการเรียงลำดับสตริงที่สิ้นสุด NUL:
find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
-E
สำหรับ MacOS
ทำไมไม่ลองอันนี้:
find / -name '*f*' -printf "%h\n" | sort -u
find
spec forนั้นค่อนข้างกระจัดกระจาย - ไม่ได้ระบุ-printf
โอเปอเรเตอร์ นี้ไม่ได้ทำงานกับ BSD ดังนั้นไม่ใช่ "POSIX ทั้งหมดที่เข้ากันได้" (แม้ว่าจะอยู่ใน POSIX )find
sort -u
โดยพื้นฐานแล้วมี 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*
นี่เรากำลังจะใช้เครื่องมือดังต่อไปนี้find
, และgrep
sort
$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/
โซ่เครื่องมือเช่นเดียวกับก่อนยกเว้นในครั้งนี้เราจะใช้แทนdirname
grep
$ 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
นี่คือสิ่งที่ฉันพบว่ามีประโยชน์:
find . -type f -name "*somefile*" | xargs dirname | sort | uniq
คำตอบนี้ขึ้นอยู่กับคำตอบ 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*
$ 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
คำสั่งสิ่งที่คุณต้องการใช้
ด้วยzsh
:
typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))
printf '%s\n' $dirs
uniq
ลงไปในส่วนผสมนั้นช่วยได้มากโดยการลบเส้นซ้ำที่อยู่ติดกันออกไปfind . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'
. หรือหากเครื่องมือของคุณเก่าไปหน่อย uniq อาจไม่มีตัวเลือก -zfind . -type f -name '*f*' -printf '%h\n' | uniq | sort -u