ฉันจะค้นหานามสกุลไฟล์ที่แตกต่างทั้งหมดในลำดับชั้นของโฟลเดอร์ได้อย่างไร


235

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

อะไรจะเป็นวิธีที่ดีที่สุดในการบรรลุเป้าหมายนี้จากเชลล์?

คำตอบ:


347

ลองใช้ (ไม่แน่ใจว่าเป็นวิธีที่ดีที่สุด แต่ใช้งานได้):

find . -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u

มันทำงานดังต่อไปนี้:

  • ค้นหาไฟล์ทั้งหมดจากโฟลเดอร์ปัจจุบัน
  • พิมพ์นามสกุลไฟล์ถ้ามี
  • ทำรายการเรียงลำดับที่ไม่ซ้ำกัน

8
เพื่อการอ้างอิงเท่านั้น: หากคุณต้องการแยกไดเรกทอรีบางส่วนออกจากการค้นหา (เช่น.svn) ให้ใช้find . -type f -path '*/.svn*' -prune -o -print | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u แหล่งข้อมูล
Dennis Golomazov

ช่องว่างจะไม่สร้างความแตกต่างใด ๆ ชื่อไฟล์แต่ละชื่อจะอยู่ในบรรทัดแยกกันดังนั้นตัวคั่นรายการไฟล์จะไม่ใช่ "\ n"
Ivan Nevostruev

1
บน Windows สิ่งนี้ทำงานได้ดีกว่าและเร็วกว่า find: dir / s / b | perl -ne 'พิมพ์ $ 1 ถ้า m /\.(ه^^.\\\\++)$/' | sort -u
Ryan Shillington

3
รูปแบบคอมไพล์ของคำตอบ: ใช้git ls-tree -r HEAD --name-onlyแทนfind
jakub.g

8
รูปแบบนี้จะแสดงรายการที่มีจำนวนต่อการขยาย:find . -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort | uniq -c | sort -n
marcovtwout

55

ไม่จำเป็นต้องใช้ท่อถึงsortawk สามารถทำได้ทั้งหมด:

find . -type f | awk -F. '!a[$NF]++{print $NF}'

ฉันไม่ได้รับสิ่งนี้ให้ทำงานเป็นนามแฝงฉันได้รับ awk: ข้อผิดพลาดทางไวยากรณ์ที่บรรทัดที่ 1 บริบทคือ >>>! a [] <<< awk: การประกันตัวที่บรรทัดที่ 1 ฉันทำอะไรผิด นามแฝงของฉันถูกกำหนดเช่นนี้: alias file_ext = "find. -type f -name ' . ' | awk -F. '! a [$ NF] ++ {พิมพ์ $ NF}'"
user2602152

2
@ user2602152 ปัญหาคือว่าคุณพยายามล้อมรอบหนึ่งซับด้วยคำพูดสำหรับaliasคำสั่ง แต่คำสั่งตัวเองใช้คำพูดในคำสั่งค้นหาแล้ว ในการแก้ไขปัญหานี้ฉันจะใช้bashไวยากรณ์สตริงตามตัวอักษรดังนี้:alias file_ext=$'find . -type f -name "*.*" | awk -F. \'!a[$NF]++{print $NF}\''
SiegeX

สิ่งนี้จะไม่ทำงานหากมีหนึ่งตำบล ในชื่อและไฟล์ไม่มีนามสกุลไฟล์ ตัวอย่าง: เมื่อเราเรียกใช้จาก maindir มันจะล้มเหลวmaindir/test.dir/myfile
Nelson Teixeira

1
@NelsonTeixeira เพิ่ม-printf "%f\n"ไปยังจุดสิ้นสุดของคำสั่ง 'find' และเรียกใช้การทดสอบของคุณอีกครั้ง
SiegeX

41

เวอร์ชันเรียกซ้ำ:

find . -type f | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort -u

หากคุณต้องการผลรวม (อาจมีการขยายครั้ง)

find . -type f | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort | uniq -c | sort -rn

ไม่เรียกซ้ำ (โฟลเดอร์เดียว):

for f in *.*; do printf "%s\n" "${f##*.}"; done | sort -u

ฉันเคยใช้โพสต์ฟอรัมนี้เครดิตควรไปที่นั่น


ที่ดี! ยังใช้งานได้กับสถานการณ์คอมไพล์ของฉันพยายามหาว่าไฟล์ประเภทใดที่ฉันได้สัมผัสในการคอมมิทล่าสุด:git show --name-only --pretty="" | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort -u
Raven vulcan

30

Powershell:

dir -recurse | select-object extension -unique

ขอบคุณhttp://kevin-berridge.blogspot.com/2007/11/windows-powershell.html


20
OP กล่าวว่า "บนเครื่อง Linux"
Forbesmyester

9
ที่จริงแล้วมี prowershell สำหรับ linux ออกตอนนี้: github.com/Microsoft/PowerShell-DSC-for-Linux
KIC

4
ตามที่เขียนไว้นี้จะเลือกไดเรกทอรีที่มี.ในนั้น (เช่นjquery-1.3.4จะแสดงขึ้นเช่นเดียวกับ.4ในผลลัพธ์) เปลี่ยนเป็นdir -file -recurse | select-object extension -uniqueรับเฉพาะนามสกุลไฟล์
mcw

1
@Forbesmyester: คนที่มี Windows (เช่นฉัน) จะพบคำถามนี้ ดังนั้นนี่คือมีประโยชน์
Roel

1
ขอบคุณสำหรับคำตอบ Powershell คุณไม่คิดว่าผู้ใช้จะค้นหาอย่างไร ผู้คนมากมายโหวตให้เหตุผล
Mahesh

20

awk-less, sed-less, Perl-less, Python-less POSIX-compliant

find . -type f | rev | cut -d. -f1 | rev  | tr '[:upper:]' '[:lower:]' | sort | uniq --count | sort -rn

เคล็ดลับคือมันฝืนเส้นและตัดส่วนขยายที่จุดเริ่มต้น
นอกจากนี้ยังแปลงส่วนขยายเป็นตัวพิมพ์เล็ก

ตัวอย่างผลลัพธ์:

   3689 jpg
   1036 png
    610 mp4
     90 webm
     90 mkv
     57 mov
     12 avi
     10 txt
      3 zip
      2 ogv
      1 xcf
      1 trashinfo
      1 sh
      1 m4v
      1 jpeg
      1 ini
      1 gqv
      1 gcs
      1 dv

บน Mac uniqไม่ได้มีธงเต็ม--countแต่-cทำงานได้ดี
worc

12

ค้นหาทุกจุดด้วยจุดและแสดงเฉพาะคำต่อท้าย

find . -type f -name "*.*" | awk -F. '{print $NF}' | sort -u

ถ้าคุณรู้ว่าคำต่อท้ายทั้งหมดมี 3 ตัวอักษรแล้ว

find . -type f -name "*.???" | awk -F. '{print $NF}' | sort -u

หรือด้วย sed แสดงคำต่อท้ายทั้งหมดที่มีอักขระหนึ่งถึงสี่ตัว เปลี่ยน {1,4} เป็นช่วงของอักขระที่คุณคาดหวังในคำต่อท้าย

find . -type f | sed -n 's/.*\.\(.\{1,4\}\)$/\1/p'| sort -u

1
ไม่จำเป็นต้องใช้ท่อเพื่อ 'เรียงลำดับ' awk สามารถทำได้ทั้งหมด: ค้นหา -type f -name " . " | awk -F '! a [$ NF] ++ {พิมพ์ $ NF}'
SiegeX

@SiegeX ของคุณควรเป็นคำตอบที่แยกต่างหาก พบว่าคำสั่งทำงานได้ดีที่สุดสำหรับโฟลเดอร์ขนาดใหญ่เนื่องจากพิมพ์ส่วนขยายตามที่พบ แต่โปรดทราบว่าควรเป็น: -name " . "
Ralf

@Ralf ทำโพสต์คำตอบที่นี่ ไม่แน่ใจเกี่ยวกับสิ่งที่คุณหมายถึง-name "."เพราะนั่นคือสิ่งที่มันมีอยู่แล้ว
SiegeX

ฉันหมายถึงมันควรเป็น -name "*. *" แต่ StackOverflow ลบอักขระ * ซึ่งอาจเกิดขึ้นในความคิดเห็นของคุณเช่นกัน
Ralf

ดูเหมือนว่านี่ควรเป็นคำตอบที่ได้รับการยอมรับ awk เป็นเครื่องมือที่ดีที่สุดในการใช้บรรทัดคำสั่ง
Jon z

7

การเพิ่มรูปแบบของตัวเองในการผสม ฉันคิดว่ามันง่ายที่สุดของล็อตและมีประโยชน์เมื่อประสิทธิภาพไม่ใช่เรื่องใหญ่

find . -type f | grep -o -E '\.[^\.]+$' | sort -u

1
+1 สำหรับการพกพาแม้ว่า regex จะค่อนข้าง จำกัด เนื่องจากตรงกับส่วนขยายที่ประกอบด้วยตัวอักษรเดียว การใช้ regex จากคำตอบที่ยอมรับนั้นน่าจะดีกว่า:$ find . -type f | grep -o -E '\.[^.\/]+$' | sort -u
mMontu

1
ตกลง ฉันออกไปเล็กน้อย แก้ไขคำตอบของฉันเพื่อแก้ไขข้อผิดพลาดที่คุณเห็น
gkb0986

เย็น. ฉัน chenge คำพูดเพื่อ doublequotes ปรับปรุง grep biraries และการอ้างอิง (เพราะให้กับคอมไพล์ล้าสมัย) และตอนนี้ทำงานภายใต้ windows รู้สึกเหมือนผู้ใช้ linux
msangel

5

ใน Python ใช้ generators สำหรับไดเรกทอรีที่มีขนาดใหญ่มากรวมถึงส่วนขยายที่ว่างเปล่าและรับจำนวนครั้งที่ส่วนขยายแต่ละส่วนแสดงขึ้น:

import json
import collections
import itertools
import os

root = '/home/andres'
files = itertools.chain.from_iterable((
    files for _,_,files in os.walk(root)
    ))
counter = collections.Counter(
    (os.path.splitext(file_)[1] for file_ in files)
)
print json.dumps(counter, indent=2)

5

ฉันลองคำตอบมากมายที่นี่แม้แต่คำตอบที่ "ดีที่สุด" พวกเขาทั้งหมดมาจากสิ่งที่ฉันเป็นเฉพาะหลังจาก ดังนั้นนอกเหนือจาก 12 ชั่วโมงที่ผ่านมาในการใช้รหัส regex สำหรับหลาย ๆ โปรแกรมและการอ่านและทดสอบคำตอบเหล่านี้นี่คือสิ่งที่ฉันคิดขึ้นมาซึ่งทำงานได้อย่างที่ฉันต้องการ

 find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{2,16}" | awk '{print tolower($0)}' | sort -u
  • ค้นหาไฟล์ทั้งหมดที่อาจมีนามสกุล
  • Greps เฉพาะส่วนขยาย
  • Greps สำหรับนามสกุลไฟล์ระหว่าง 2 ถึง 16 ตัวอักษร (เพียงปรับตัวเลขหากไม่ตรงกับความต้องการของคุณ) ซึ่งจะช่วยหลีกเลี่ยงไฟล์แคชและไฟล์ระบบ (ระบบไฟล์บิตคือการค้นหาคุก)
  • ต้องพิมพ์ส่วนขยายด้วยตัวพิมพ์เล็ก
  • จัดเรียงและนำค่าที่ไม่ซ้ำกันเท่านั้น เดิมฉันพยายามลองคำตอบ awk แต่มันจะพิมพ์สองครั้งที่แตกต่างกันในกรณีที่ไว

หากคุณต้องการจำนวนนามสกุลไฟล์ให้ใช้รหัสด้านล่าง

find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{2,16}" | awk '{print tolower($0)}' | sort | uniq -c | sort -rn

ในขณะที่วิธีการเหล่านี้จะใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์และอาจไม่ใช่วิธีที่ดีที่สุดในการแก้ปัญหา แต่ก็ใช้งานได้

อัปเดต: นามสกุลไฟล์ยาว @ @ alpha_989 จะทำให้เกิดปัญหา นั่นเป็นเพราะ regex ดั้งเดิม "[[: alpha:]] {3,6}" ฉันได้อัพเดตคำตอบเพื่อรวม regex "[[: alpha:]] {2,16}" อย่างไรก็ตามทุกคนที่ใช้รหัสนี้ควรทราบว่าตัวเลขเหล่านั้นเป็นจำนวนต่ำสุดและสูงสุดของระยะเวลาที่อนุญาตให้ส่วนขยายสำหรับผลลัพธ์สุดท้าย สิ่งใดก็ตามที่อยู่นอกช่วงนั้นจะถูกแบ่งออกเป็นหลายบรรทัดในเอาต์พุต

หมายเหตุ: โพสต์ดั้งเดิมอ่านแล้ว "- Greps สำหรับนามสกุลไฟล์ระหว่าง 3 และ 6 ตัวอักษร (เพียงปรับตัวเลขหากไม่พอดีกับความต้องการของคุณ) ซึ่งจะช่วยหลีกเลี่ยงไฟล์แคชและไฟล์ระบบ (บิตระบบไฟล์คือการค้นหาคุก) "

แนวคิด: สามารถใช้เพื่อค้นหาไฟล์นามสกุลในระยะเวลาที่กำหนดผ่าน:

 find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{4,}" | awk '{print tolower($0)}' | sort -u

โดยที่ 4 คือความยาวของนามสกุลไฟล์ที่จะรวมและจากนั้นค้นหาส่วนขยายที่เกินความยาวนั้น


เวอร์ชันการนับซ้ำซ้ำหรือไม่
Fernando Montoya

@Shinrai โดยทั่วไปทำงานได้ดี แต่ถ้าคุณมีนามสกุลไฟล์สุ่มที่ยาวมากเช่น. download มันจะแบ่ง ".download" ออกเป็น 2 ส่วนและรายงานไฟล์ 2 ไฟล์ซึ่งเป็น "downlo" และอีกอันคือ "ad"
alpha_989

@ alpha_989 นั่นเป็นเพราะ regex "[[: alpha:]] {3,6}" จะทำให้เกิดปัญหากับส่วนขยายที่เล็กกว่า 3 ตัวอักษร ปรับให้เข้ากับสิ่งที่คุณต้องการ โดยส่วนตัวฉันจะบอกว่า 2,168 ควรทำงานในกรณีส่วนใหญ่
Shinrai

ขอบคุณที่ตอบกลับ .. ใช่ .. นั่นคือสิ่งที่ฉันรู้ในภายหลัง มันทำงานได้ดีหลังจากที่ฉันแก้ไขมันคล้ายกับที่คุณพูดถึง
alpha_989

3

เนื่องจากมีวิธีแก้ไขปัญหาอื่นซึ่งใช้ Perl:

หากคุณติดตั้ง Python คุณสามารถทำได้ (จากเชลล์):

python -c "import os;e=set();[[e.add(os.path.splitext(f)[-1]) for f in fn]for _,_,fn in os.walk('/home')];print '\n'.join(e)"

2

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

import os, sys

def names(roots):
    for root in roots:
        for a, b, basenames in os.walk(root):
            for basename in basenames:
                yield basename

sufs = set(os.path.splitext(x)[1] for x in names(sys.argv[1:]))
for suf in sufs:
    if suf:
        print suf

2

ฉันไม่คิดว่าอันนี้พูดถึง:

find . -type f -exec sh -c 'echo "${0##*.}"' {} \; | sort | uniq -c

นี่อาจจะค่อนข้างช้าเนื่องจากการวางไข่กระบวนการใหม่สำหรับแต่ละไฟล์
Ondra Žižka

1

ฉันคิดว่าวิธีที่ง่ายและตรงไปตรงมาที่สุดคือ

for f in *.*; do echo "${f##*.}"; done | sort -u

มันแก้ไขในวิธีที่ 3 ของ ChristopheD



0

ฉันพบว่ามันง่ายและรวดเร็ว ...

   # find . -type f -exec basename {} \; | awk -F"." '{print $NF}' > /tmp/outfile.txt
   # cat /tmp/outfile.txt | sort | uniq -c| sort -n > tmp/outfile_sorted.txt

0

คำตอบที่ยอมรับนั้นใช้ REGEX และคุณไม่สามารถสร้างคำสั่ง alias ด้วย REGEX คุณต้องใส่มันลงในเชลล์สคริปต์ฉันใช้ Amazon Linux 2 และทำสิ่งต่อไปนี้:

  1. ฉันใส่รหัสคำตอบที่ยอมรับลงในไฟล์โดยใช้:

    sudo เป็นกลุ่ม find.sh

เพิ่มรหัสนี้:

find ./ -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u

บันทึกไฟล์โดยพิมพ์: :wq!

  1. sudo vim ~/.bash_profile

  2. alias getext=". /path/to/your/find.sh"

  3. :wq!

  4. . ~/.bash_profile

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