Soru Linux'ta C'deki dizinleri sıralı olarak nasıl listeleyebilirim?


C programlamadaki tüm dizinleri ve dosyaları yinelemeli olarak listelemem gerekiyor. FTW'yi inceledim ama bu benim kullandığım 2 işletim sistemine dahil edilmedi (Fedora ve Minix). Son birkaç saat boyunca okuduğum tüm farklı şeylerden büyük bir baş ağrısına başlıyorum.

Birisi bir kod parçacığı bilirse, bu harika olurdu, ya da kimse bana bu konuda iyi bir yön verebilir eğer çok minnettar olurdu.


44
2017-12-08 19:57


Menşei


Neden bunu sadece bir betik dilinde yapmıyorsunuz? Bu daha hızlı ve yazması daha kolay olurdu. - dbeer
@dbeer C programında bu bilgiye ihtiyacı varsa ne olur?
Eylemi yinelemeli olarak gerçekleştirmek istediğinizden emin misiniz? Döngüsel bağların ve açık dosya sınırlarının yinelemeli uygulamalar için bir sorun oluşturabileceğine işaret ediyorum. Bağlantılı bir liste (veya iki) kullanmayı düşünürüm, bu nedenle kod önceden işlenmiş klasörlere karşı kontrol edebilir. Bu, kodun, derin hiyerarşileri geçerken tek bir açık dosya kullanmasına da izin verecektir. - Myst


Cevaplar:


İşte özyinelemeli bir versiyon:

#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>

void listdir(const char *name, int indent)
{
    DIR *dir;
    struct dirent *entry;

    if (!(dir = opendir(name)))
        return;

    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == DT_DIR) {
            char path[1024];
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
            printf("%*s[%s]\n", indent, "", entry->d_name);
            listdir(path, indent + 2);
        } else {
            printf("%*s- %s\n", indent, "", entry->d_name);
        }
    }
    closedir(dir);
}

int main(void) {
    listdir(".", 0);
    return 0;
}

49
2017-12-08 22:34



<Dirent.h> 'de tanımlanmalıdır. Bunu hangi platformda derliyorsunuz? - Lloyd Macrohon
fedora altında gcc. - Charlie
aslında ben didnt kullanmıştım IDE ile derleyici yerleşik, terminalde GCC ile ince koştu - Charlie
Oh BTW, bu kodu değiştirin (while (= entry = readdir (dir)) ve if (! (Entry = readdir (dir)) veya en azından readdir başarısız olursa, geri dönmeden önce closedir aradığınızdan emin olun. - Lloyd Macrohon
Bu döngüsel bağlantılar varlığında nasıl davranıyor? - Kerrek SB


Neden herkes tekrar tekerleği yeniden icat etmek konusunda ısrar ediyor?

POSIX.1-2008 standartlaştırılmıştır nftw() Tek Unix Spesifikasyonu v4'te (SuSv4) tanımlanan ve Linux'ta mevcut olan işlev (glibc, man 3 nftw), OS X ve en güncel BSD varyantları. Hiç yeni değil.

naif opendir()/readdir()/closedir() temelli uygulamalar neredeyse hiçbir zaman dizinlerin veya dosyaların taşındığı, yeniden adlandırıldığı veya ağaç geçişi sırasında silinmiş durumları ele almaz. nftw() onları nazikçe ele almalı.

Örnek olarak, geçerli çalışma dizininde başlayan dizin ağacını veya komut satırında adlandırılan dizinlerin her birinde ya da yalnızca komut satırında belirtilen dosyaları listeleyen aşağıdaki C programını düşünün:

/* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */
#define _XOPEN_SOURCE 700

/* Added on 2017-06-25:
   If the C library can support 64-bit file sizes
   and offsets, using the standard names,
   these defines tell the C library to do so. */
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64 

#include <stdlib.h>
#include <unistd.h>
#include <ftw.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

/* POSIX.1 says each process has at least 20 file descriptors.
 * Three of those belong to the standard streams.
 * Here, we use a conservative estimate of 15 available;
 * assuming we use at most two for other uses in this program,
 * we should never run into any problems.
 * Most trees are shallower than that, so it is efficient.
 * Deeper trees are traversed fine, just a bit slower.
 * (Linux allows typically hundreds to thousands of open files,
 *  so you'll probably never see any issues even if you used
 *  a much higher value, say a couple of hundred, but
 *  15 is a safe, reasonable value.)
*/
#ifndef USE_FDS
#define USE_FDS 15
#endif

int print_entry(const char *filepath, const struct stat *info,
                const int typeflag, struct FTW *pathinfo)
{
    /* const char *const filename = filepath + pathinfo->base; */
    const double bytes = (double)info->st_size; /* Not exact if large! */
    struct tm mtime;

    localtime_r(&(info->st_mtime), &mtime);

    printf("%04d-%02d-%02d %02d:%02d:%02d",
           mtime.tm_year+1900, mtime.tm_mon+1, mtime.tm_mday,
           mtime.tm_hour, mtime.tm_min, mtime.tm_sec);

    if (bytes >= 1099511627776.0)
        printf(" %9.3f TiB", bytes / 1099511627776.0);
    else
    if (bytes >= 1073741824.0)
        printf(" %9.3f GiB", bytes / 1073741824.0);
    else
    if (bytes >= 1048576.0)
        printf(" %9.3f MiB", bytes / 1048576.0);
    else
    if (bytes >= 1024.0)
        printf(" %9.3f KiB", bytes / 1024.0);
    else
        printf(" %9.0f B  ", bytes);

    if (typeflag == FTW_SL) {
        char   *target;
        size_t  maxlen = 1023;
        ssize_t len;

        while (1) {

            target = malloc(maxlen + 1);
            if (target == NULL)
                return ENOMEM;

            len = readlink(filepath, target, maxlen);
            if (len == (ssize_t)-1) {
                const int saved_errno = errno;
                free(target);
                return saved_errno;
            }
            if (len >= (ssize_t)maxlen) {
                free(target);
                maxlen += 1024;
                continue;
            }

            target[len] = '\0';
            break;
        }

        printf(" %s -> %s\n", filepath, target);
        free(target);

    } else
    if (typeflag == FTW_SLN)
        printf(" %s (dangling symlink)\n", filepath);
    else
    if (typeflag == FTW_F)
        printf(" %s\n", filepath);
    else
    if (typeflag == FTW_D || typeflag == FTW_DP)
        printf(" %s/\n", filepath);
    else
    if (typeflag == FTW_DNR)
        printf(" %s/ (unreadable)\n", filepath);
    else
        printf(" %s (unknown)\n", filepath);

    return 0;
}


int print_directory_tree(const char *const dirpath)
{
    int result;

    /* Invalid directory path? */
    if (dirpath == NULL || *dirpath == '\0')
        return errno = EINVAL;

    result = nftw(dirpath, print_entry, USE_FDS, FTW_PHYS);
    if (result >= 0)
        errno = result;

    return errno;
}

int main(int argc, char *argv[])
{
    int arg;

    if (argc < 2) {

        if (print_directory_tree(".")) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }

    } else {

        for (arg = 1; arg < argc; arg++) {
            if (print_directory_tree(argv[arg])) {
                fprintf(stderr, "%s.\n", strerror(errno));
                return EXIT_FAILURE;
            }
        }

    }

    return EXIT_SUCCESS;
}

Yukarıdaki kodun çoğu print_entry(). Onun görevi her bir dizin girişini yazdırmaktır. İçinde print_directory_tree(), söyleriz nftw() her dizin girişi için onu aramak için görür.

Yukarıdaki tek el-dalgalı detay, kaç dosya tanımlayıcısına izin verilmesi gerektiğine dair karardır. nftw() kullanın. Eğer programınız dosya ağacı yürüyüşü sırasında en fazla iki dosya tanıtıcısı kullanıyorsa (standart akışlara ek olarak), 15 güvenlidir (tüm sistemlerde). nftw() ve çoğunlukla POSIX uyumlu).

Linux'ta kullanabilirsiniz sysconf(_SC_OPEN_MAX) maksimum açık dosya sayısını bulmak ve aynı anda kullandığınız numarayı çıkarmak nftw() Arayın, ama rahatsız etmeyeceğim (yararın çoğunlukla patolojik olarak derin dizin yapılarıyla kullanılacağını bilmedikçe). Onbeş tanımlayıcı yapar değil ağaç derinliğini sınırlamak; nftw() Sadece yavaşlar (ve bir dizindeki değişiklikleri bir dizinden 13 dizinden daha derine götürürse, değişikliklerin algılanması ve genel yetenekleri sistem ve C kütüphanesi uygulamaları arasında değişse de). Sadece bir derleme zamanı sabiti kullanmak, kodu taşınabilir tutar - sadece Linux'ta değil, Mac OS X'te ve tüm mevcut BSD varyantlarında ve diğer pek çok eski Unix modelinde de çalışmalıdır.

Bir yorumda, Ruslan geçiş yapmaları gerektiğini belirtti. nftw64() çünkü 64 bit boyut / ofset gerektiren dosya sistemi girdileri ve "normal" sürümleri vardı nftw() ile başarısız oldu errno == EOVERFLOW. Doğru çözüm, GLIBC'ye özgü 64-bit işlevlere geçmek değil, tanımlamaktır. _LARGEFILE64_SOURCE ve _FILE_OFFSET_BITS 64. Bunlar, standart fonksiyonlar kullanılırken C kütüphanesinin mümkünse 64 bit dosya boyutlarına ve ofsetlere geçmesini söyler.nftw(), fstat(), et cetera) ve tip isimleri (off_t vb.).


61
2018-04-01 23:37



Bu tekerleği yeniden icat etmenin temel nedeni, kullanımında bir egzersiz olarak ayarlanmış olmasıdır. opendir(), readdir(), closedir()ve nesne, insanlara tam yol adları ve dizin girişleri hakkında dikkatli düşünmeyi öğretmektir. Üretim kodunda, nftw() gitmek için yol - beni yanlış anlamayın. Ancak, ham fonksiyonları kullanan egzersiz için pedagojik bir sebep de var. - Jonathan Leffler
@moodboom: Tüm dosya sistemi aslında, gayri-güvenli bir global, brüt. Sadece bir Kodlayıcı Guru'nun globals'ın brüt olduğunu belirttiği için geçerli kullanımları olmadığı anlamına gelmez. Dizin ağacı geçişi doğru şekilde basit değil ve gördüğüm en doğru örnekleri en iç çiçekim kadar kırılgan. Çok kırılgan. Kullanmak istiyorum fts POSIX'te tanımlanmışsa, ancak alassa, yalnızca 4.4BSD'dir ve destek dağıtılmıştır. GNU C kütüphanesinde bile, destek (örneğin, büyük dosyalar için) 2.23'e kadar erimiştir. Bağlantılı tartışma sadece aptalca. - Nominal Animal
@moodboom: Doğru. Cevabımın ana noktasının, tipik kullanım örneğidir. opendir()/readdir()/closedir() güvensiz ve ırklara eğilimli ve yürüyüş devam ederken ilgili dizinleri hareket ettirirseniz gerçekten garip şeyler yapabilir. Bu hatalı kod. Kırılgan. nftw() ve diğ. Bunların hepsini doğru bir şekilde ele almaları gerekiyor. POSIX.1-2008 kullanarak güvenli bir dizin ağacı yürüteç yazabiliriz openat()/fdopendir()/readdir()/closedir()Ancak yapabileceğimiz ağaç derinliği, kullanabileceğimiz dosya tanımlayıcılarının sayısıyla sınırlı olacaktır. nftw() Bunu bile güvenli bir şekilde önlemek gerekir. - Nominal Animal
@gsamaras: :) Seni kara tahtadan tanırım; e-postayla gönderdik ve her şey, kafes eşleşmeleri ve benzeri konular hakkında konuştuk. E-posta adresimi kaybettiyseniz (artık pano kullanmıyorum - cevaplarım görünüşe göre istenmeyen, çok uzun ve çok okunacak kadar ayrıntılı), her zaman bana ulaşan birini bulabilirsin. benim ana sayfam. - Nominal Animal
Alternatifleri bile bilmiyordum. *dir Bu gönderiyi görmeden önce fonksiyonlar. Bir milyona teşekkürler. - Daniel Kamil Kozar


int is_directory_we_want_to_list(const char *parent, char *name) {
  struct stat st_buf;
  if (!strcmp(".", name) || !strcmp("..", name))
    return 0;
  char *path = alloca(strlen(name) + strlen(parent) + 2);
  sprintf(path, "%s/%s", parent, name);
  stat(path, &st_buf);
  return S_ISDIR(st_buf.st_mode);
}

int list(const char *name) {
  DIR *dir = opendir(name);
  struct dirent *ent;
  while (ent = readdir(dir)) {
    char *entry_name = ent->d_name;
    printf("%s\n", entry_name);
    if (is_directory_we_want_to_list(name, entry_name)) {
      // You can consider using alloca instead.
      char *next = malloc(strlen(name) + strlen(entry_name) + 2);
      sprintf(next, "%s/%s", name, entry_name);
      list(next);
      free(next);
    }
  }
  closedir(dir);
}

Üstbilgi dosyaları bu bağlamda eksik kalmaya değer: stat.h, dirent.h. Yukarıdaki kodun oluşabilecek hataları kontrol etmediğini unutmayın.

Tamamen farklı bir yaklaşım sunuluyor ftw ftw.h.


7
2017-12-08 20:05



tekrarlamıyorsun. en azından ona benzemiyor. Aramak mı demek istediniz list(entry_name) Yorumunuzun altında? - Jon
@Jon, bu doğru, OP'nin başlamasına yardım etmek için bir iskelet yazdım. Eğer yeterli değilse, detay verebilirim. - Jan
Bu, bir dizindeki tüm dosyaları, tüm alt dizinlerin ve alt dizinlerin içindeki tüm dosyaların yanında listeleyecek midir? - Charlie
Bu bir snippet'in başlangıcı. Eklenmesi gereken başka şeylere yorum yaptı. Yayınlandığı gibi dosyaya karşı herhangi bir yönlendirme yok, ya da dizinin . veya ..ve özyinelemeli bir çağrı yok, bu yüzden sadece verilen dizindeki dosyaları basacağını düşünüyorum. - prelic
Cevabı güncelledim. Şimdi verilen dizinin içeriğini listeler ve alt dizinlere is_directory_we_want_to_list sıfır olmayan bir değer döndürür. HTH. - Jan


Benim yorumumda da belirttiğim gibi, bu göreve ilişkin iki içsel kusura sahip olmak için özyinelemeli bir yaklaşıma inanıyorum.

İlk kusur açık dosyalardaki sınırdır. Bu sınır, derin traversal bir sınır uygular. Yeterli alt klasör varsa, özyineli yaklaşım bozulur. (Yığın taşmasıyla ilgili düzenlemeyi görme)

İkinci kusur biraz daha incelikli. Yinelemeli yaklaşım, sert bağlantıların test edilmesini çok zorlaştırır. Bir klasör ağacı döngüsel (sabit bağlantılar nedeniyle) ise, özyinelemeli yaklaşım kırılır (umarım yığılma olmadan taşınır). (Sabit bağlantılarla ilgili düzenlemeyi görün)

Ancak, bu sorunları tek bir dosya tanıtıcı ve bağlantılı listelerle değiştirerek bu sorunlardan kaçınmak oldukça kolaydır.

Bunun bir okul projesi olmadığını ve yinelemenin isteğe bağlı olduğunu varsayalım.

İşte bir örnek uygulama.

kullanım a.out ./ klasör ağacını görüntülemek için

Makrolar ve şeyler için özür dilerim ... Genellikle satır içi işlevleri kullanırım, ancak tek bir işlevde olsaydı kodu takip etmenin daha kolay olacağını düşündüm.

#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char const *argv[]) {
  /* print use instruction unless a folder name was given */
  if (argc < 2)
    fprintf(stderr,
            "\nuse:\n"
            "    %s <directory>\n"
            "for example:\n"
            "    %s ./\n\n",
            argv[0], argv[0]),
        exit(0);

  /*************** a small linked list macro implementation ***************/

  typedef struct list_s {
    struct list_s *next;
    struct list_s *prev;
  } list_s;

#define LIST_INIT(name)                                                        \
  { .next = &name, .prev = &name }

#define LIST_PUSH(dest, node)                                                  \
  do {                                                                         \
    (node)->next = (dest)->next;                                               \
    (node)->prev = (dest);                                                     \
    (node)->next->prev = (node);                                               \
    (dest)->next = (node);                                                     \
  } while (0);

#define LIST_POP(list, var)                                                    \
  if ((list)->next == (list)) {                                                \
    var = NULL;                                                                \
  } else {                                                                     \
    var = (list)->next;                                                        \
    (list)->next = var->next;                                                  \
    var->next->prev = var->prev;                                               \
  }

  /*************** a record (file / folder) item type ***************/

  typedef struct record_s {
    /* this is a flat processing queue. */
    list_s queue;
    /* this will list all queued and processed folders (cyclic protection) */
    list_s folders;
    /* this will list all the completed items (siblings and such) */
    list_s list;
    /* unique ID */
    ino_t ino;
    /* name length */
    size_t len;
    /* name string */
    char name[];
  } record_s;

/* take a list_s pointer and convert it to the record_s pointer */
#define NODE2RECORD(node, list_name)                                           \
  ((record_s *)(((uintptr_t)(node)) -                                          \
                ((uintptr_t) & ((record_s *)0)->list_name)))

/* initializes a new record */
#define RECORD_INIT(name)                                                      \
  (record_s){.queue = LIST_INIT((name).queue),                                 \
             .folders = LIST_INIT((name).folders),                             \
             .list = LIST_INIT((name).list)}

  /*************** the actual code ***************/

  record_s records = RECORD_INIT(records);
  record_s *pos, *item;
  list_s *tmp;
  DIR *dir;
  struct dirent *entry;

  /* initialize the root folder record and add it to the queue */
  pos = malloc(sizeof(*pos) + strlen(argv[1]) + 2);
  *pos = RECORD_INIT(*pos);
  pos->len = strlen(argv[1]);
  memcpy(pos->name, argv[1], pos->len);
  if (pos->name[pos->len - 1] != '/')
    pos->name[pos->len++] = '/';
  pos->name[pos->len] = 0;
  /* push to queue, but also push to list (first item processed) */
  LIST_PUSH(&records.queue, &pos->queue);
  LIST_PUSH(&records.list, &pos->list);

  /* as long as the queue has items to be processed, do so */
  while (records.queue.next != &records.queue) {
    /* pop queued item */
    LIST_POP(&records.queue, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, queue);
    /* add record to the processed folder list */
    LIST_PUSH(&records.folders, &pos->folders);

    /* process the folder and add all folder data to current list */
    dir = opendir(pos->name);
    if (!dir)
      continue;

    while ((entry = readdir(dir)) != NULL) {

      /* create new item, copying it's path data and unique ID */
      item = malloc(sizeof(*item) + pos->len + entry->d_namlen + 2);
      *item = RECORD_INIT(*item);
      item->len = pos->len + entry->d_namlen;
      memcpy(item->name, pos->name, pos->len);
      memcpy(item->name + pos->len, entry->d_name, entry->d_namlen);
      item->name[item->len] = 0;
      item->ino = entry->d_ino;
      /* add item to the list, right after the `pos` item */
      LIST_PUSH(&pos->list, &item->list);

      /* unless it's a folder, we're done. */
      if (entry->d_type != DT_DIR)
        continue;

      /* test for '.' and '..' */
      if (entry->d_name[0] == '.' &&
          (entry->d_name[1] == 0 ||
           (entry->d_name[1] == '.' && entry->d_name[2] == 0)))
        continue;

      /* add folder marker */
      item->name[item->len++] = '/';
      item->name[item->len] = 0;

      /* test for cyclic processing */
      list_s *t = records.folders.next;
      while (t != &records.folders) {
        if (NODE2RECORD(t, folders)->ino == item->ino) {
          /* we already processed this folder! */
          break; /* this breaks from the small loop... */
        }
        t = t->next;
      }
      if (t != &records.folders)
        continue; /* if we broke from the small loop, entry is done */

      /* item is a new folder, add to queue */
      LIST_PUSH(&records.queue, &item->queue);
    }
    closedir(dir);
  }

  /*************** Printing the results and cleaning up ***************/
  while (records.list.next != &records.list) {
    /* pop list item */
    LIST_POP(&records.list, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, list);
    /* prepare for next iteration */
    LIST_POP(&records.list, tmp);
    fwrite(pos->name, pos->len, 1, stderr);
    fwrite("\n", 1, 1, stderr);
    free(pos);
  }
  return 0;
}

DÜZENLE

@Stargateur, yorumlarda, özyinel kodun açık dosya limitine ulaşmadan önce yığının taşma olasılığının üstesinden geleceğini belirtti.

Yığın taşmasının nasıl daha iyi olduğunu görmeme rağmen, bu değerlendirme, işlem çağrıldığında dosya sınırına yakın olmadığı sürece muhtemelen doğrudur.

Yorumlarda @Stargateur tarafından bahsedilen bir başka nokta, özyinelemeli kodun derinliğinin, alt dizinlerin maksimum miktarı (ext4 dosya sistemindeki 64000) ile sınırlı olması ve sabit bağlantıların son derece olası olmamasıdır ( Linux / Unix'e izin verilir).

Kod Linux üzerinde çalışıyorsa (ki bu soruya göre), bu iyi bir haber, bu yüzden bu konu gerçek bir endişe değil (MacOS veya belki de Windows üzerinde kod çalıştırılmadıkça) ... 64K alt klasörleri olmasına rağmen Yinelemede yığının genişliği geniş olabilir.

Bununla birlikte, özyinelemeli seçeneğin hala, sonuçların önbelleğe alınabilmesinin yanı sıra işlenen öğelerin miktarına kolayca bir sınır ekleyebilmesi gibi avantajları da vardır.

Not;

Yorumlara göre, döngüsel hiyerarşileri kontrol etmeyen kodun yinelemeli olmayan bir sürümü. Daha hızlıdır ve klasörlere sabit bağlantılara izin verilmeyen bir Linux makinesinde kullanılabilecek kadar güvenli olmalıdır.

#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char const *argv[]) {
  /* print use instruction unless a folder name was given */
  if (argc < 2)
    fprintf(stderr,
            "\nuse:\n"
            "    %s <directory>\n"
            "for example:\n"
            "    %s ./\n\n",
            argv[0], argv[0]),
        exit(0);

  /*************** a small linked list macro implementation ***************/

  typedef struct list_s {
    struct list_s *next;
    struct list_s *prev;
  } list_s;

#define LIST_INIT(name)                                                        \
  { .next = &name, .prev = &name }

#define LIST_PUSH(dest, node)                                                  \
  do {                                                                         \
    (node)->next = (dest)->next;                                               \
    (node)->prev = (dest);                                                     \
    (node)->next->prev = (node);                                               \
    (dest)->next = (node);                                                     \
  } while (0);

#define LIST_POP(list, var)                                                    \
  if ((list)->next == (list)) {                                                \
    var = NULL;                                                                \
  } else {                                                                     \
    var = (list)->next;                                                        \
    (list)->next = var->next;                                                  \
    var->next->prev = var->prev;                                               \
  }

  /*************** a record (file / folder) item type ***************/

  typedef struct record_s {
    /* this is a flat processing queue. */
    list_s queue;
    /* this will list all the completed items (siblings and such) */
    list_s list;
    /* unique ID */
    ino_t ino;
    /* name length */
    size_t len;
    /* name string */
    char name[];
  } record_s;

/* take a list_s pointer and convert it to the record_s pointer */
#define NODE2RECORD(node, list_name)                                           \
  ((record_s *)(((uintptr_t)(node)) -                                          \
                ((uintptr_t) & ((record_s *)0)->list_name)))

/* initializes a new record */
#define RECORD_INIT(name)                                                      \
  (record_s){.queue = LIST_INIT((name).queue), .list = LIST_INIT((name).list)}

  /*************** the actual code ***************/

  record_s records = RECORD_INIT(records);
  record_s *pos, *item;
  list_s *tmp;
  DIR *dir;
  struct dirent *entry;

  /* initialize the root folder record and add it to the queue */
  pos = malloc(sizeof(*pos) + strlen(argv[1]) + 2);
  *pos = RECORD_INIT(*pos);
  pos->len = strlen(argv[1]);
  memcpy(pos->name, argv[1], pos->len);
  if (pos->name[pos->len - 1] != '/')
    pos->name[pos->len++] = '/';
  pos->name[pos->len] = 0;
  /* push to queue, but also push to list (first item processed) */
  LIST_PUSH(&records.queue, &pos->queue);
  LIST_PUSH(&records.list, &pos->list);

  /* as long as the queue has items to be processed, do so */
  while (records.queue.next != &records.queue) {
    /* pop queued item */
    LIST_POP(&records.queue, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, queue);

    /* process the folder and add all folder data to current list */
    dir = opendir(pos->name);
    if (!dir)
      continue;

    while ((entry = readdir(dir)) != NULL) {

      /* create new item, copying it's path data and unique ID */
      item = malloc(sizeof(*item) + pos->len + entry->d_namlen + 2);
      *item = RECORD_INIT(*item);
      item->len = pos->len + entry->d_namlen;
      memcpy(item->name, pos->name, pos->len);
      memcpy(item->name + pos->len, entry->d_name, entry->d_namlen);
      item->name[item->len] = 0;
      item->ino = entry->d_ino;
      /* add item to the list, right after the `pos` item */
      LIST_PUSH(&pos->list, &item->list);

      /* unless it's a folder, we're done. */
      if (entry->d_type != DT_DIR)
        continue;

      /* test for '.' and '..' */
      if (entry->d_name[0] == '.' &&
          (entry->d_name[1] == 0 ||
           (entry->d_name[1] == '.' && entry->d_name[2] == 0)))
        continue;

      /* add folder marker */
      item->name[item->len++] = '/';
      item->name[item->len] = 0;

      /* item is a new folder, add to queue */
      LIST_PUSH(&records.queue, &item->queue);
    }
    closedir(dir);
  }

  /*************** Printing the results and cleaning up ***************/
  while (records.list.next != &records.list) {
    /* pop list item */
    LIST_POP(&records.list, tmp);
    /* collect record to process */
    pos = NODE2RECORD(tmp, list);
    /* prepare for next iteration */
    LIST_POP(&records.list, tmp);
    fwrite(pos->name, pos->len, 1, stderr);
    fwrite("\n", 1, 1, stderr);
    free(pos);
  }
  return 0;
}

4
2018-06-27 07:25



@Stargateur, eğer özyinelemeli kod bir sunucuda çalışıyorsa, muhtemelen doğrudur - bu durumda, yığının bozulmadan saklanması ve dosya tanımlayıcılarının kullanımını en aza indirgeme olasılığı büyüktür. Ayrıca, bir döngüsel klasör hiyerarşisi varsa, alt dizin sınırı sizi kurtaramaz ... Bununla birlikte, bağlantılı liste yaklaşımı yığın taşmasını önler ve asla asılmayacak veya çökmeyecektir (büyük bir ağaç işlerken bile). İşlenen öğelerin sayısına bir sınır eklemek de oldukça kolaydır. Basitliği için özyinelemeli kodları seviyorum, ancak genellikle kullanımı daha tehlikelidir ve yığını tehdit eder. - Myst
@Stargateur ne sert bağlantılar hakkında ...? Bunlar bir risk değil midir, yoksa işletim sistemi her zaman çevrimsel hiyerarşilerin oluşturulmasını engelliyor mu? - Myst
MacOS, resmi olarak izin vermez, ancak TimeMachine içinde kullanılırlar ve oluşturulabilirler (her ne kadar katman tarafından olmasa da). Bu yüzden, bu sabit bağlantıların ortaya çıkabileceği bir sistem biliyorum (bunlar, oluşturuldukları için veya koddan dolayı) TimeMachine yedeklemelerini içeren bir klasörde çalışır) ... Ancak, risk olasılığı düşük olabilir. - Myst
@Stargateur - Cevabınızı yorumlarınızı yansıtacak şekilde güncelledim. - Myst


İşte özyineleme kullanan ancak daha az yığın alanı kullanan basitleştirilmiş bir sürüm:

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>

void listdir(char *path, size_t size) {
    DIR *dir;
    struct dirent *entry;
    size_t len = strlen(path);

    if (!(dir = opendir(path))) {
        fprintf(stderr, "path not found: %s: %s\n",
                path, strerror(errno));
        return;
    }

    puts(path);
    while ((entry = readdir(dir)) != NULL) {
        char *name = entry->d_name;
        if (entry->d_type == DT_DIR) {
            if (!strcmp(name, ".") || !strcmp(name, ".."))
                continue;
            if (len + strlen(name) + 2 > size) {
                fprintf(stderr, "path too long: %s/%s\n", path, name);
            } else {
                path[len] = '/';
                strcpy(path + len + 1, name);
                listdir(path, size);
                path[len] = '\0';
            }
        } else {
            printf("%s/%s\n", path, name);
        }
    }
    closedir(dir);
}

int main(void) {
    char path[1024] = ".";
    listdir(path, sizeof path);
    return 0;
}

Benim sistemimde, çıktısı tam olarak aynıdır. find .


2
2018-06-24 16:12



chqrlie, Charlie'ye cevap verir, bu karışıktır: s. - Stargateur
@Stargateur: evet, ben de fark ettim ama hiçbir bağlantı yok. - chqrlie