ไฟล์ Linux ที่รวดเร็วนับสำหรับไฟล์จำนวนมาก


142

ฉันกำลังพยายามหาวิธีที่ดีที่สุดในการค้นหาจำนวนไฟล์ในไดเร็กทอรีหนึ่ง ๆ เมื่อมีไฟล์จำนวนมาก (มากกว่า 100,000 ไฟล์)

เมื่อมีไฟล์จำนวนมากการดำเนินการls | wc -lใช้เวลาค่อนข้างนานในการดำเนินการ ฉันเชื่อว่านี่เป็นเพราะมันส่งคืนชื่อของไฟล์ทั้งหมด ฉันพยายามใช้ดิสก์ I / O ให้น้อยที่สุด

ฉันได้ทดลองกับเชลล์และสคริปต์ Perl บางส่วนแล้วก็ไม่มีประโยชน์ ฉันจะทำมันได้อย่างไร?


2
ตรวจสอบให้แน่ใจว่า "ls" ของคุณคือ / usr / bin / ls ไม่ใช่นามแฝงของสิ่งที่น่าสนใจ
glenn jackman

คำถามที่คล้ายกันพร้อมคำตอบที่น่าสนใจที่นี่: serverfault.com/questions/205071/…
aidan

สิ่งที่ควรค่าแก่การชี้ให้เห็นว่าส่วนใหญ่ถ้าไม่ใช่วิธีแก้ปัญหาทั้งหมดที่นำเสนอสำหรับคำถามนี้ไม่ได้เฉพาะเจาะจงกับLinuxแต่เป็นเรื่องทั่วไปสำหรับระบบที่คล้ายกับ NIX ทั้งหมด บางทีการลบแท็ก "Linux" ก็เหมาะสม
Christopher Schultz

คำตอบ:


196

โดยค่าเริ่มต้นจะlsเรียงลำดับชื่อซึ่งอาจใช้เวลาสักครู่หากมีจำนวนมาก นอกจากนี้จะไม่มีเอาต์พุตจนกว่าจะอ่านและเรียงลำดับชื่อทั้งหมด ใช้ls -fตัวเลือกเพื่อปิดการเรียงลำดับ

ls -f | wc -l

ทราบว่านี้ยังจะช่วยให้-aเพื่อให้., ..และไฟล์อื่น ๆ ที่เริ่มต้นด้วย.จะถูกนับ


12
+1 lsและฉันคิดว่าฉันรู้ทุกอย่างมีความรู้เกี่ยวกับ
ม็อบ

6
ZOMG การเรียงลำดับ 100K บรรทัดไม่มีอะไรเลยเมื่อเทียบกับการstat()โทรlsในทุกไฟล์ จึงfindไม่stat()ทำงานได้เร็วขึ้น
Dummy00001

13
ls -fไม่stat()เช่นกัน แต่แน่นอนทั้งสองlsและfindการเรียกร้องstat()เมื่อตัวเลือกบางอย่างจะใช้เช่นหรือls -l find -mtime
mark4o

8
สำหรับบริบทนี้ใช้เวลา 1-2 นาทีในการนับ 2.5 ล้าน jpgs บนกล่อง Slicehost ขนาดเล็ก
philfreo

6
หากคุณต้องการเพิ่มไดเรกทอรีย่อยในการนับให้ทำls -fR | wc -l
Ryan Walls

63

วิธีที่เร็วที่สุดคือโปรแกรมที่สร้างขึ้นโดยเฉพาะเช่นนี้:

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count = 0;

    dir = opendir(argv[1]);

    while((ent = readdir(dir)))
            ++count;

    closedir(dir);

    printf("%s contains %ld files\n", argv[1], count);

    return 0;
}

จากการทดสอบของฉันโดยไม่คำนึงถึงแคชฉันรันแต่ละรายการประมาณ 50 ครั้งต่อไดเรกทอรีเดียวกันซ้ำแล้วซ้ำเล่าเพื่อหลีกเลี่ยงการบิดเบือนข้อมูลที่อิงกับแคชและฉันได้รับตัวเลขประสิทธิภาพโดยประมาณต่อไปนี้ (ตามเวลานาฬิกาจริง):

ls -1  | wc - 0:01.67
ls -f1 | wc - 0:00.14
find   | wc - 0:00.22
dircnt | wc - 0:00.04

สุดท้ายdircntคือโปรแกรมที่รวบรวมจากแหล่งข้อมูลข้างต้น

แก้ไข 26-09-2016

เนื่องจากความต้องการที่ได้รับความนิยมฉันจึงเขียนโปรแกรมนี้ขึ้นมาใหม่เพื่อให้เรียกซ้ำดังนั้นโปรแกรมจะลดลงในไดเรกทอรีย่อยและนับไฟล์และไดเรกทอรีแยกกัน

เนื่องจากเห็นได้ชัดว่าบางคนต้องการทราบวิธีการทั้งหมดนี้ฉันจึงมีความคิดเห็นมากมายในโค้ดเพื่อพยายามทำให้ชัดเจนว่าเกิดอะไรขึ้น ฉันเขียนสิ่งนี้และทดสอบบน Linux 64 บิต แต่ควรทำงานได้กับระบบที่รองรับ POSIX รวมถึง Microsoft Windows ยินดีต้อนรับรายงานข้อผิดพลาด ฉันยินดีที่จะอัปเดตสิ่งนี้หากคุณไม่สามารถทำงานบน AIX หรือ OS / 400 หรืออะไรก็ได้

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

มีการตรวจสอบข้อผิดพลาดน้อยมากและcountตัวฟังก์ชันเองก็ไม่ได้รายงานข้อผิดพลาดจริงๆ การโทรเท่านั้นที่สามารถล้มเหลวได้คือopendirและstat(หากคุณไม่โชคดีและมีระบบที่direntมีประเภทไฟล์อยู่แล้ว) ฉันไม่ paranoid เกี่ยวกับการตรวจสอบความยาวรวมของ pathnames subdir แต่ในทางทฤษฎีระบบไม่ควรอนุญาตให้ชื่อเส้นทางใด ๆ PATH_MAXที่มีความยาวเกินกว่า หากมีข้อกังวลฉันสามารถแก้ไขได้ แต่เป็นเพียงโค้ดเพิ่มเติมที่ต้องอธิบายให้คนที่เรียนรู้ที่จะเขียน C โปรแกรมนี้มีจุดมุ่งหมายเพื่อเป็นตัวอย่างของการดำน้ำในไดเรกทอรีย่อยแบบวนซ้ำ

#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/stat.h>

#if defined(WIN32) || defined(_WIN32) 
#define PATH_SEPARATOR '\\' 
#else
#define PATH_SEPARATOR '/' 
#endif

/* A custom structure to hold separate file and directory counts */
struct filecount {
  long dirs;
  long files;
};

/*
 * counts the number of files and directories in the specified directory.
 *
 * path - relative pathname of a directory whose files should be counted
 * counts - pointer to struct containing file/dir counts
 */
void count(char *path, struct filecount *counts) {
    DIR *dir;                /* dir structure we are reading */
    struct dirent *ent;      /* directory entry currently being processed */
    char subpath[PATH_MAX];  /* buffer for building complete subdir and file names */
    /* Some systems don't have dirent.d_type field; we'll have to use stat() instead */
#if !defined ( _DIRENT_HAVE_D_TYPE )
    struct stat statbuf;     /* buffer for stat() info */
#endif

/* fprintf(stderr, "Opening dir %s\n", path); */
    dir = opendir(path);

    /* opendir failed... file likely doesn't exist or isn't a directory */
    if(NULL == dir) {
        perror(path);
        return;
    }

    while((ent = readdir(dir))) {
      if (strlen(path) + 1 + strlen(ent->d_name) > PATH_MAX) {
          fprintf(stdout, "path too long (%ld) %s%c%s", (strlen(path) + 1 + strlen(ent->d_name)), path, PATH_SEPARATOR, ent->d_name);
          return;
      }

/* Use dirent.d_type if present, otherwise use stat() */
#if defined ( _DIRENT_HAVE_D_TYPE )
/* fprintf(stderr, "Using dirent.d_type\n"); */
      if(DT_DIR == ent->d_type) {
#else
/* fprintf(stderr, "Don't have dirent.d_type, falling back to using stat()\n"); */
      sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
      if(lstat(subpath, &statbuf)) {
          perror(subpath);
          return;
      }

      if(S_ISDIR(statbuf.st_mode)) {
#endif
          /* Skip "." and ".." directory entries... they are not "real" directories */
          if(0 == strcmp("..", ent->d_name) || 0 == strcmp(".", ent->d_name)) {
/*              fprintf(stderr, "This is %s, skipping\n", ent->d_name); */
          } else {
              sprintf(subpath, "%s%c%s", path, PATH_SEPARATOR, ent->d_name);
              counts->dirs++;
              count(subpath, counts);
          }
      } else {
          counts->files++;
      }
    }

/* fprintf(stderr, "Closing dir %s\n", path); */
    closedir(dir);
}

int main(int argc, char *argv[]) {
    struct filecount counts;
    counts.files = 0;
    counts.dirs = 0;
    count(argv[1], &counts);

    /* If we found nothing, this is probably an error which has already been printed */
    if(0 < counts.files || 0 < counts.dirs) {
        printf("%s contains %ld files and %ld directories\n", argv[1], counts.files, counts.dirs);
    }

    return 0;
}

แก้ไข 2017-01-17

ฉันได้รวมการเปลี่ยนแปลงสองอย่างที่แนะนำโดย @FlyingCodeMonkey:

  1. ใช้lstatแทนstat. สิ่งนี้จะเปลี่ยนลักษณะการทำงานของโปรแกรมหากคุณมีไดเร็กทอรี symlinked ในไดเร็กทอรีที่คุณกำลังสแกน พฤติกรรมก่อนหน้านี้คือไดเร็กทอรีย่อย (ที่เชื่อมโยง) จะมีการเพิ่มจำนวนไฟล์ลงในจำนวนโดยรวม ลักษณะการทำงานใหม่คือไดเร็กทอรีที่เชื่อมโยงจะนับเป็นไฟล์เดียวและเนื้อหาจะไม่ถูกนับ
  2. หากเส้นทางของไฟล์ยาวเกินไปข้อความแสดงข้อผิดพลาดจะปรากฏขึ้นและโปรแกรมจะหยุดทำงาน

แก้ไข 2017-06-29

ด้วยความโชคดีนี่จะเป็นการแก้ไขครั้งสุดท้ายของคำตอบนี้ :)

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

แหล่งที่มามีอยู่ใน Apache License 2.0 แพทช์* ยินดีต้อนรับ!


  • "ปะ" คือสิ่งที่คนแก่อย่างฉันเรียกว่า "pull request"

2
ดีเพียง! ขอบคุณ! และสำหรับผู้ที่ไม่ทราบ: คุณสามารถปฏิบัติตามรหัสด้านบนในเทอร์มินัล: gcc -o dircnt dircnt.cและการใช้งานจะเป็นเช่นนี้./dircnt some_dir
aesede

มีวิธีง่ายๆในการทำให้เกิดซ้ำหรือไม่?
ck_

@ck_ แน่นอนว่าสามารถทำซ้ำได้อย่างง่ายดาย คุณต้องการความช่วยเหลือในการแก้ปัญหาหรือคุณต้องการให้ฉันเขียนเรื่องทั้งหมด?
Christopher Schultz

1
@ChristopherSchultz เกณฑ์มาตรฐานที่คุณโพสต์ไว้ข้างต้น - ไดเรกทอรีที่มีปัญหาใหญ่แค่ไหน
Dom Vinyard

1
ฉันต้องการใช้สิ่งนี้ใน Python จริงๆดังนั้นฉันจึงบรรจุเป็นแพ็คเกจffcount ขอบคุณที่ทำให้รหัสพร้อมใช้งาน @ChristopherSchultz!
GjjvdBurg

35

ใช้หา ตัวอย่างเช่น:

find . -name "*.ext" | wc -l

1
สิ่งนี้จะค้นหาไฟล์ซ้ำ ๆภายใต้ไดเร็กทอรีปัจจุบัน
mark4o

ในระบบของฉันfind /usr/share | wc -l(~ 137,000 ไฟล์) เร็วกว่าประมาณ 25% ls -R /usr/share | wc -l(ประมาณ 160,000 บรรทัดรวมถึงชื่อ dir ผลรวม dir และบรรทัดว่าง) ในการรันครั้งแรกของแต่ละไฟล์และเร็วกว่าอย่างน้อยสองเท่าเมื่อเปรียบเทียบการรัน (แคช) ที่ตามมา
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

12
ถ้าเขาต้องการเฉพาะไดเร็กทอรีปัจจุบันไม่ใช่ทั้งทรีวนซ้ำเขาสามารถเพิ่ม -maxdepth 1 ตัวเลือกเพื่อค้นหา
igustin

3
ดูเหมือนว่าเหตุผลที่findจะเร็วกว่าเป็นเพราะวิธีการที่คุณกำลังใช้ls lsหากคุณหยุดการเรียงลำดับlsและfindมีประสิทธิภาพใกล้เคียงกัน
Christopher Schultz

คุณสามารถเพิ่มความเร็วในการค้นหา + wc ได้โดยการพิมพ์อักขระเพียงตัวเดียว: find . -printf x | wc -c. มิฉะนั้นคุณกำลังสร้างสตริงจากเส้นทางทั้งหมดและส่งต่อไปยัง wc (I / O เพิ่มเติม)
ives

18

find , lsและperl ที่ทดสอบกับไฟล์ 40,000 ไฟล์มีความเร็วเท่ากัน (แม้ว่าฉันไม่ได้พยายามล้างแคช):

[user@server logs]$ time find . | wc -l
42917

real    0m0.054s
user    0m0.018s
sys     0m0.040s

[user@server logs]$ time /bin/ls -f | wc -l
42918

real    0m0.059s
user    0m0.027s
sys     0m0.037s

และด้วยopendirและreaddirของ Perl ในเวลาเดียวกัน:

[user@server logs]$ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"'
42918

real    0m0.057s
user    0m0.024s
sys     0m0.033s

หมายเหตุ: ฉันใช้ / bin / ls -f เพื่อให้แน่ใจว่าได้ข้ามตัวเลือกนามแฝงซึ่งอาจทำให้ช้าลงเล็กน้อยและ-fเพื่อหลีกเลี่ยงการเรียงลำดับไฟล์ lsโดยไม่-fช้ากว่าfind/ สองเท่าperl ยกเว้นถ้าlsใช้กับ-fดูเหมือนว่าจะเป็นเวลาเดียวกัน:

[user@server logs]$ time /bin/ls . | wc -l
42916

real    0m0.109s
user    0m0.070s
sys     0m0.044s

ฉันยังต้องการมีสคริปต์เพื่อถามระบบไฟล์โดยตรงโดยไม่มีข้อมูลที่ไม่จำเป็นทั้งหมด

การทดสอบจะขึ้นอยู่กับคำตอบของปีเตอร์แวนเดอร์ Heijden , เกล็น Jackmanและmark4o


7
แน่นอนคุณควรล้างแคชระหว่างการทดสอบ ครั้งแรกที่ฉันเรียกใช้ls -l | wc -lโฟลเดอร์บน HDD ภายนอก 2.5 "ที่มีไฟล์ 1M ใช้เวลาประมาณ 3 นาทีเพื่อให้การดำเนินการเสร็จสิ้นครั้งที่สองใช้เวลา 12 วินาที IIRC นอกจากนี้อาจขึ้นอยู่กับระบบไฟล์ของคุณด้วยเช่นกัน ใช้Btrfs.
เบห์รัง Saeedzadeh

ขอบคุณข้อมูลโค้ด Perl เป็นวิธีแก้ปัญหาสำหรับฉัน $ time perl -e 'opendir D, "."; @files = readdir D; closedir D; print scalar(@files)."\n"' 1315029 real 0m0.580s user 0m0.302s sys 0m0.275s
Pažout

คุณสามารถเพิ่มความเร็วในการค้นหา + wc ได้โดยการพิมพ์อักขระเพียงตัวเดียว: find . -printf x | wc -c. มิฉะนั้นคุณกำลังสร้างสตริงจากเส้นทางทั้งหมดและส่งต่อไปยัง wc (I / O เพิ่มเติม)
ives

6

น่าแปลกสำหรับฉันการค้นพบกระดูกที่เปลือยเปล่านั้นเทียบได้กับ ls -f

> time ls -f my_dir | wc -l
17626

real    0m0.015s
user    0m0.011s
sys     0m0.009s

เทียบกับ

> time find my_dir -maxdepth 1 | wc -l
17625

real    0m0.014s
user    0m0.008s
sys     0m0.010s

แน่นอนว่าค่าของทศนิยมตำแหน่งที่สามจะเปลี่ยนไปรอบ ๆ เล็กน้อยทุกครั้งที่คุณเรียกใช้สิ่งเหล่านี้ดังนั้นค่าเหล่านี้จึงเหมือนกันโดยทั่วไป อย่างไรก็ตามโปรดสังเกตว่าfindจะส่งคืนหน่วยพิเศษหนึ่งหน่วยเนื่องจากจะนับไดเร็กทอรีจริงเอง (และตามที่กล่าวไว้ก่อนหน้านี้ls -fจะส่งคืนหน่วยพิเศษสองหน่วยเนื่องจากจะนับด้วยและ .. )


5

คุณสามารถเปลี่ยนผลลัพธ์ตามความต้องการของคุณได้ แต่นี่คือ Bash one-liner ที่ฉันเขียนเพื่อนับซ้ำและรายงานจำนวนไฟล์ในชุดของไดเร็กทอรีที่มีชื่อเป็นตัวเลข

dir=/tmp/count_these/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$i => $(find ${dir}${i} -type f | wc -l),"; }

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

ผลลัพธ์จะเป็นดังนี้:

1 => 38,
65 => 95052,
66 => 12823,
67 => 10572,
69 => 67275,
70 => 8105,
71 => 42052,
72 => 1184,

2
ฉันพบว่าตัวอย่างค่อนข้างสับสนเล็กน้อย ฉันสงสัยว่าทำไมมีตัวเลขทางด้านซ้ายแทนที่จะเป็นชื่อไดเรกทอรี ขอบคุณสำหรับสิ่งนี้ฉันลงเอยด้วยการปรับแต่งเล็กน้อยเล็กน้อย (นับไดเร็กทอรีและวางชื่อโฟลเดอร์ฐานสำหรับ i ใน $ (ls -1. | sort -n); {echo "$ i => $ (find $ {i} | wc -l)";}
TheJacobTaylor

1
ตัวเลขทางด้านซ้ายคือชื่อไดเร็กทอรีของฉันจากข้อมูลตัวอย่างของฉัน ขออภัยที่ทำให้สับสน
mightybs

1
ls -1 ${dir}จะทำงานไม่ถูกต้องหากไม่มีช่องว่างมากขึ้น นอกจากนี้ยังไม่มีการรับประกันว่าชื่อที่ส่งคืนlsจะสามารถส่งผ่านไปยังfindได้เนื่องจากlsจะใช้อักขระที่ไม่สามารถพิมพ์ได้เพื่อหลีกเลี่ยงการบริโภคของมนุษย์ ( mkdir $'oddly\nnamed\ndirectory'หากคุณต้องการกรณีทดสอบที่น่าสนใจเป็นพิเศษ) ดูเหตุผลที่คุณไม่ควรแยกวิเคราะห์ผลลัพธ์ของ ls (1)
Charles Duffy

5

จำนวนไฟล์ Linux ที่รวดเร็ว

จำนวนไฟล์ Linux ที่เร็วที่สุดที่ฉันรู้คือ

locate -c -r '/home'

นอกจากนี้ยังไม่มีความจำเป็นที่จะเรียกgrep ! แต่ดังที่กล่าวไว้คุณควรมีฐานข้อมูลใหม่ (อัปเดตทุกวันโดยงาน cron หรือด้วยตนเองsudo updatedb)

จากมนุษย์ค้นหา

-c, --count
    Instead  of  writing  file  names on standard output, write the number of matching
    entries only.

นอกจากนี้คุณควรทราบว่ามันยังนับไดเรกทอรีเป็นไฟล์!


BTW:หากคุณต้องการภาพรวมของไฟล์และไดเร็กทอรีของคุณในประเภทระบบของคุณ

locate -S

แสดงจำนวนไดเร็กทอรีไฟล์และอื่น ๆ


1
โปรดทราบว่าคุณต้องตรวจสอบให้แน่ใจว่าฐานข้อมูลเป็นปัจจุบัน
phuclv

1
ฮ่า ๆ ถ้าคุณมีการนับทั้งหมดในฐานข้อมูลอยู่แล้วคุณสามารถนับได้อย่างรวดเร็ว :)
Christopher Schultz

4

คุณสามารถรับจำนวนไฟล์และไดเร็กทอรีด้วยโปรแกรมต้นไม้

เรียกใช้คำสั่งtree | tail -n 1เพื่อรับบรรทัดสุดท้ายซึ่งจะบอกว่า "763 ไดเร็กทอรี, 9290 ไฟล์" ซึ่งจะนับไฟล์และโฟลเดอร์แบบวนซ้ำโดยไม่รวมไฟล์ที่ซ่อนอยู่ซึ่งสามารถเพิ่มได้ด้วยแฟล็-aก สำหรับการอ้างอิงคอมพิวเตอร์ของฉันใช้เวลา 4.8 วินาทีในการนับ Tree เพื่อนับโฮมไดเร็กทอรีทั้งหมดของฉันซึ่งเป็น 24,777 ไดเร็กทอรี 238,680 ไฟล์ find -type f | wc -lใช้เวลา 5.3 วินาทีครึ่งวินาทีนานขึ้นดังนั้นฉันคิดว่าต้นไม้ค่อนข้างฉลาดในการแข่งขัน

ตราบเท่าที่คุณไม่มีโฟลเดอร์ย่อยต้นไม้เป็นวิธีที่ง่ายและรวดเร็วในการนับไฟล์

นอกจากนี้และหมดจดเพื่อความสนุกสนานของมันคุณสามารถใช้tree | grep '^├'เพื่อแสดงเฉพาะไฟล์ / โฟลเดอร์ในไดเรกทอรีปัจจุบัน - lsนี้เป็นพื้นรุ่นช้าลงมาก


Brew install tailสำหรับ OS X.
The Unfun Cat

@TheUnfunCat tailควรได้รับการติดตั้งบนระบบ Mac OS X ของคุณแล้ว
Christopher Schultz

2

คำตอบนี้เร็วกว่าเกือบทุกอย่างในหน้านี้สำหรับไดเรกทอรีที่มีขนาดใหญ่มากและซ้อนกันมาก:

https://serverfault.com/a/691372/84703

locate -r '.' | grep -c "^$PWD"


2
ดี. เนื่องจากคุณมีฐานข้อมูลล่าสุดของไฟล์ทั้งหมดอยู่แล้วจึงไม่จำเป็นต้องไปที่มันอีก แต่น่าเสียดายที่คุณต้องตรวจสอบให้แน่ใจว่าคำสั่ง updatedb ทำงานและเสร็จสมบูรณ์สำหรับวิธีนี้แล้ว
Chris Reid

คุณไม่จำเป็นต้อง grep ใช้locate -c -r '/path'like ในโซลูชันของ
abu_bua

2

คุณสามารถลองถ้าใช้opendir()และreaddir()ในPerlเร็วกว่า สำหรับตัวอย่างของฟังก์ชั่นเหล่านั้นให้ดูที่นี่


2
การใช้งาน: perl -e 'opendir D, "."; @ ไฟล์ = readdir D; ปิด D; print scalar (@files) '
glenn jackman

2

ฉันมาที่นี่เมื่อพยายามนับไฟล์ในชุดข้อมูลประมาณ 10,000 โฟลเดอร์โดยมีไฟล์ประมาณ 10,000 ไฟล์ ปัญหาของหลาย ๆ วิธีคือโดยปริยายสถิติ 100 ล้านไฟล์ซึ่งใช้เวลานาน

ฉันใช้เสรีภาพในการขยายแนวทางของ Christopher Schultzดังนั้นจึงสนับสนุนการส่งผ่านไดเรกทอรีผ่านอาร์กิวเมนต์ (วิธีการเรียกซ้ำของเขาใช้สถิติเช่นกัน)

ใส่สิ่งต่อไปนี้ลงในไฟล์dircnt_args.c:

#include <stdio.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *ent;
    long count;
    long countsum = 0;
    int i;

    for(i=1; i < argc; i++) {
        dir = opendir(argv[i]);
        count = 0;
        while((ent = readdir(dir)))
            ++count;

        closedir(dir);

        printf("%s contains %ld files\n", argv[i], count);
        countsum += count;
    }
    printf("sum: %ld\n", countsum);

    return 0;
}

หลังจากนั้นgcc -o dircnt_args dircnt_args.cคุณสามารถเรียกมันได้ดังนี้:

dircnt_args /your/directory/*

ใน 100 ล้านไฟล์ใน 10,000 โฟลเดอร์ข้างต้นจะเสร็จสิ้นอย่างรวดเร็ว (ประมาณ 5 นาทีสำหรับการรันครั้งแรกและการติดตามแคช: ประมาณ 23 วินาที)

วิธีเดียวที่อื่น ๆ ที่ดำเนินการเสร็จสิ้นในเวลาน้อยกว่าหนึ่งชั่วโมงก็lsมีประมาณ 1 ls -f /your/directory/* | wc -lนาทีในแคช: จำนวนถูกปิดโดยบรรทัดใหม่สองสามรายการต่อไดเรกทอรีแม้ว่า ...

นอกเหนือจากที่คาดไว้ไม่มีความพยายามใด ๆ ของฉันที่findส่งคืนภายในหนึ่งชั่วโมง: - /


สำหรับใครที่ไม่ใช่โปรแกรมเมอร์ C คุณสามารถอธิบายได้ไหมว่าทำไมถึงเร็วกว่านี้และจะได้รับคำตอบแบบเดียวกันโดยไม่ทำสิ่งเดียวกันได้อย่างไร
mlissner

คุณไม่จำเป็นต้องเป็นโปรแกรมเมอร์ C เพียงแค่ทำความเข้าใจความหมายของการสร้างสถิติไฟล์และวิธีการแสดงไดเร็กทอรี: ไดเร็กทอรีคือรายชื่อไฟล์และ inodes โดยพื้นฐานแล้ว หากคุณสร้างไฟล์ที่คุณเข้าถึงไอโหนดซึ่งอยู่ที่ไหนสักแห่งในไดรฟ์เพื่อรับข้อมูลเช่นขนาดไฟล์สิทธิ์ ... หากคุณสนใจแค่จำนวนต่อ dir คุณไม่จำเป็นต้องเข้าถึงข้อมูลไอโหนดซึ่งอาจช่วยให้คุณประหยัดเวลาได้มาก
Jörn Hees

segfaults นี้บน Oracle linux, gcc เวอร์ชัน 4.8.5 20150623 (Red Hat 4.8.5-28.0.1) (GCC) ... เส้นทางสัมพัทธ์และ fs ระยะไกลดูเหมือนจะเป็นสาเหตุ
Rondo

เรื่อง "นับปิดโดยคู่ของการขึ้นบรรทัดใหม่ต่อไดเรกทอรีแม้ว่า"นี้สามารถแก้ไขได้โดยการรวม-fกับ-A(ตัวพิมพ์ใหญ่ ls -f -A'a'): ตัวเลือก-fเปิดใช้งาน-a(ตัวพิมพ์เล็ก 'a') แต่สามารถแทนที่ด้วย-A. ทดสอบกับlsเวอร์ชัน 8.30
Peter Mortensen

2

วิธีที่เร็วที่สุดบน Linux (คำถามถูกแท็กเป็น Linux) คือการใช้การเรียกระบบโดยตรง นี่คือโปรแกรมเล็ก ๆ ที่นับไฟล์ (เท่านั้นไม่มีไดเร็กทอรี) ในไดเร็กทอรี คุณสามารถนับล้านของไฟล์และมันก็เป็นประมาณ 2.5 เท่าเร็วกว่า ls "-f" และรอบ 1.3-1.5 ครั้งเร็วกว่าคำตอบที่คริสชูลทซ์ของ

#define _GNU_SOURCE
#include <dirent.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/syscall.h>

#define BUF_SIZE 4096

struct linux_dirent {
    long d_ino;
    off_t d_off;
    unsigned short d_reclen;
    char d_name[];
};

int countDir(char *dir) {

    int fd, nread, bpos, numFiles = 0;
    char d_type, buf[BUF_SIZE];
    struct linux_dirent *dirEntry;

    fd = open(dir, O_RDONLY | O_DIRECTORY);
    if (fd == -1) {
        puts("open directory error");
        exit(3);
    }
    while (1) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
        if (nread == -1) {
            puts("getdents error");
            exit(1);
        }
        if (nread == 0) {
            break;
        }

        for (bpos = 0; bpos < nread;) {
            dirEntry = (struct linux_dirent *) (buf + bpos);
            d_type = *(buf + bpos + dirEntry->d_reclen - 1);
            if (d_type == DT_REG) {
                // Increase counter
                numFiles++;
            }
            bpos += dirEntry->d_reclen;
        }
    }
    close(fd);

    return numFiles;
}

int main(int argc, char **argv) {

    if (argc != 2) {
        puts("Pass directory as parameter");
        return 2;
    }
    printf("Number of files in %s: %d\n", argv[1], countDir(argv[1]));
    return 0;
}

PS: ไม่ใช่การเรียกซ้ำ แต่คุณสามารถแก้ไขเพื่อให้บรรลุสิ่งนั้นได้


1
ฉันไม่แน่ใจว่าฉันยอมรับว่าเร็วกว่านี้ ฉันไม่ได้ตรวจสอบทุกอย่างที่คอมไพเลอร์ทำกับopendir/ readdirแต่ฉันสงสัยว่ามันจะเดือดจนเกือบจะเป็นรหัสเดียวกันในที่สุด การเรียกระบบด้วยวิธีนั้นจึงไม่สามารถพกพาได้และเนื่องจาก Linux ABI ไม่เสถียรโปรแกรมที่คอมไพล์ในระบบหนึ่งจึงไม่รับประกันว่าจะทำงานได้อย่างถูกต้องในอีกระบบหนึ่ง (แม้ว่าจะเป็นคำแนะนำที่ดีพอสมควรในการรวบรวมสิ่งใด ๆ จากแหล่งที่มาบน IMO ของระบบ * NIX ). หากความเร็วเป็นสิ่งสำคัญนี่เป็นทางออกที่ดีหากมันช่วยเพิ่มความเร็วได้จริง - ฉันไม่ได้เปรียบเทียบโปรแกรมแยกกัน
Christopher Schultz

1

lsใช้เวลามากขึ้นในการเรียงลำดับชื่อไฟล์ ใช้-fเพื่อปิดการจัดเรียงจะช่วยประหยัดเวลา:

ls -f | wc -l

หรือคุณสามารถใช้find:

find . -type f | wc -l

1

คุณควรใช้ "getdents" แทน ls / find

นี่เป็นบทความที่ดีมากบทความหนึ่งซึ่งอธิบายถึงแนวทางการรับ

http://be-n.com/spw/you-can-list-a-million-files-in-a-directory-but-not-with-ls.html

นี่คือสารสกัด:

lsและวิธีอื่น ๆ ในการแสดงรายการไดเร็กทอรี (รวมถึงos.listdirของ Python และfind .) อาศัย libc readdir () อย่างไรก็ตาม readdir () อ่านรายการไดเร็กทอรี 32K ต่อครั้งเท่านั้นซึ่งหมายความว่าหากคุณมีไฟล์จำนวนมากในไดเร็กทอรีเดียวกัน (เช่น 500 ล้านรายการไดเร็กทอรี) จะต้องใช้เวลานานพอสมควรในการอ่านทั้งหมด รายการไดเรกทอรีโดยเฉพาะบนดิสก์ช้า สำหรับไดเรกทอรีที่มีไฟล์จำนวนมากคุณจะต้องเจาะลึกกว่าเครื่องมือที่ใช้ readdir () คุณจะต้องใช้ getdents () สายระบบโดยตรงมากกว่าวิธีการช่วยเหลือจากห้องสมุดมาตรฐาน C

เราสามารถค้นหารหัส C เพื่อแสดงรายการไฟล์โดยใช้ getdents () ได้จากที่นี่ :

มีการปรับเปลี่ยนสองอย่างที่คุณต้องทำเพื่อให้แสดงรายการไฟล์ทั้งหมดในไดเร็กทอรีได้อย่างรวดเร็ว

ขั้นแรกให้เพิ่มขนาดบัฟเฟอร์จาก X เป็น 5 เมกะไบต์

#define BUF_SIZE 1024*1024*5

จากนั้นแก้ไขลูปหลักที่มันพิมพ์ข้อมูลเกี่ยวกับแต่ละไฟล์ในไดเร็กทอรีเพื่อข้ามรายการด้วย inode == 0 ฉันทำได้โดยการเพิ่ม

if (dp->d_ino != 0) printf(...);

ในกรณีของฉันฉันดูแลเฉพาะชื่อไฟล์ในไดเร็กทอรีดังนั้นฉันจึงเขียนคำสั่ง printf () ใหม่เพื่อพิมพ์เฉพาะชื่อไฟล์

if(d->d_ino) printf("%sn ", (char *) d->d_name);

คอมไพล์ (ไม่จำเป็นต้องมีไลบรารีภายนอกดังนั้นจึงทำได้ง่ายมาก)

gcc listdir.c -o listdir

ตอนนี้เพียงแค่เรียกใช้

./listdir [directory with an insane number of files]

1
โปรดทราบว่า Linux ทำการอ่านล่วงหน้าดังนั้นจึงreaddir()ไม่ช้า ฉันต้องการหุ่นที่แข็งแรงก่อนที่จะเชื่อว่ามันคุ้มค่าที่จะทิ้งความสามารถในการพกพาเพื่อเพิ่มประสิทธิภาพนี้
fuz

คุณสามารถเพิ่มเกณฑ์มาตรฐานเปรียบเทียบทั้งสองวิธีได้หรือไม่? รวม ภายใต้เงื่อนไขใดเช่นจำนวนไฟล์แคชของระบบไฟล์แบบเย็น / อุ่นฮาร์ดแวร์ประเภทดิสก์ (HDD กับ SSD) ประเภทระบบไฟล์ (เช่น ext4 หรือ NTFS) สถานะการกระจายตัวของดิสก์ระบบคอมพิวเตอร์และระบบปฏิบัติการ (เช่น Ubuntu 16.04) พร้อมข้อมูลเวอร์ชัน))? คุณสามารถแก้ไขคำตอบของคุณได้ (แต่ไม่มี "แก้ไข:", "อัปเดต:" หรือคล้ายกัน)
Peter Mortensen

ขอบเขตของ getdents () คืออะไร? สำหรับ Linux เท่านั้น?
Peter Mortensen

0

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

ls -1 /path/to/dir > count.txt && cat count.txt | wc -l

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

คุณสามารถเพิ่มการวัดจริงสำหรับสองวิธี (ไฟล์ไปป์และไฟล์กลาง) ให้กับคำตอบของคุณได้หรือไม่ (รวมถึงเงื่อนไขเช่นจำนวนไฟล์ฮาร์ดแวร์ประเภทดิสก์ (HDD เทียบกับ SSD) ประเภทระบบไฟล์ (เช่นext4หรือNTFS ) สถานะการกระจายตัวของดิสก์ระบบคอมพิวเตอร์และระบบปฏิบัติการ (เช่นUbuntu 16.04 ) พร้อมข้อมูลเวอร์ชัน))? คุณสามารถแก้ไขคำตอบของคุณได้ (แต่ไม่มี "แก้ไข:", "อัปเดต:" หรือคล้ายกัน)
Peter Mortensen

-2

ไดเร็กทอรี 10 รายการแรกที่มีจำนวนไฟล์มากที่สุด

dir=/ ; for i in $(ls -1 ${dir} | sort -n) ; { echo "$(find ${dir}${i} \
    -type f | wc -l) => $i,"; } | sort -nr | head -10

3
นี้แน่นอนดูอย่างน่าอัศจรรย์คล้ายกับคำตอบ (ที่มีข้อบกพร่องเดียวกัน) เขียนโดย mightybs หากคุณกำลังจะขยายหรือแก้ไขโค้ดที่เขียนโดยบุคคลอื่นการให้เครดิตนั้นเหมาะสม การทำความเข้าใจรหัสที่คุณใช้ในคำตอบของคุณเพียงพอที่จะระบุและแก้ไขข้อบกพร่องของมันนั้นเหมาะสมยิ่งกว่า
Charles Duffy

-2

ฉันชอบคำสั่งต่อไปนี้เพื่อติดตามการเปลี่ยนแปลงของจำนวนไฟล์ในไดเร็กทอรี

watch -d -n 0.01 'ls | wc -l'

คำสั่งจะเปิดหน้าต่างไว้เพื่อติดตามจำนวนไฟล์ที่อยู่ในไดเร็กทอรีด้วยอัตราการรีเฟรช 0.1 วินาที


คุณแน่ใจหรือไม่ว่าls | wc -lจะเสร็จสิ้นสำหรับโฟลเดอร์ที่มีไฟล์หลายพันหรือหลายล้านไฟล์ใน 0.01 วินาที แม้ว่าคุณlsจะไม่มีประสิทธิภาพอย่างมากเมื่อเทียบกับโซลูชันอื่น ๆ และ OP เพียงแค่ต้องการรับการนับไม่ได้นั่งอยู่ที่นั่นเพื่อดูผลลัพธ์ที่เปลี่ยนไป
phuclv

ดี. ดี. ฉันพบวิธีแก้ปัญหาที่สวยงามซึ่งเหมาะกับฉัน ฉันอยากจะแบ่งปันเหมือนกันดังนั้นจึงทำ ฉันไม่รู้ว่าคำสั่ง 'ls' ใน linux ไม่มีประสิทธิภาพสูง คุณใช้อะไรแทนสิ่งนั้น? และ 0.01 วินาทีคืออัตราการรีเฟรช ไม่ใช่เวลา. หากคุณไม่ได้ใช้นาฬิกาโปรดดูหน้าคน
Anoop Toffy

ฉันได้อ่านwatchคู่มือหลังจากความคิดเห็นนั้นและเห็นว่า 0.01 วินาที (ไม่ใช่ 0.1 วินาที) เป็นตัวเลขที่ไม่สมจริงเนื่องจากอัตราการรีเฟรชของหน้าจอพีซีส่วนใหญ่อยู่ที่ 60Hz เท่านั้นและสิ่งนี้ไม่ได้ตอบคำถาม แต่อย่างใด OP ถามเกี่ยวกับ "Fast Linux File Count สำหรับไฟล์จำนวนมาก" คุณยังไม่ได้อ่านคำตอบก่อนโพสต์
phuclv

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