Neden ls -R, “özyinelemeli” liste deniyor?


36

ls -RDizinlerin bir listesini gösterir anlıyorum . Ama neden özyinelemeli? Süreçte özyineleme nasıl kullanılır?


12
Sezgi, dizinlerin ve alt dizinlerinin bir ağaç kullanılarak kolayca modellenebilmesidir. Ağaçları yürüyüş algoritmaları tipik olarak özyinelemelidir.
Kevin - Monica

1
@Kevin Her bir soruyu cevaplamak için ağaç kavramını çağırmanın bir gereği olduğunu sanmıyorum - cevap, lsbir dizinle karşılaştığında, bu dizini yinelemeli olarak listelemesidir.
user253751

Yanıtlar:


67

Öncelikle, rasgele bir klasör yapısı tanımlayalım:

.
├── a1 [D]
│   ├── b1 [D]
│   │   ├── c1
│   │   ├── c2 [D]
│   │   │   ├── d1
│   │   │   ├── d2
│   │   │   └── d3
│   │   ├── c3
│   │   ├── c4
│   │   └── c5
│   └── b2 [D]
│       ├── c6
│       └── c7
├── a2 [D]
│   ├── b3 [D]
│   │   ├── c8
│   │   └── c9
│   └── b4 [D]
│       ├── c10 
│       └── c11
├── a3 [D]
│   ├── b5
│   ├── b6
│   └── b7
└── a4 [D]

Bunu yaptığımızda ls, sadece temel klasörün çıktısını alırız:

a1 a2 a3 a4

Ancak, aradığımızda ls -Rfarklı bir şey buluruz:

.:
a1  a2  a3  a4

./a1:
b1  b2

./a1/b1:
c1  c2  c3  c4  c5

./a1/b1/c2:
d1  d2  d3

./a1/b2:
c6  c7

./a2:
b3  b4

./a2/b3:
c8  c9

./a2/b4:
c10  c11

./a3:
b5  b6  b7

./a4:

Gördüğünüz gibi ls, temel klasörde ve ardından tüm alt klasörlerde çalışıyor. Ve tüm torun klasörler, reklam sonsuz. Etkili, komut her klasörün geçiyor yinelemeli o dizin ağacının sonuna ulaşıncaya kadar. Bu noktada, ağaçta bir dal oluşturur ve varsa alt klasörler için aynı şeyi yapar.

Veya sözde kodda:

recursivelyList(directory) {
    files[] = listDirectory(directory)              // Get all files in the directory
    print(directory.name + ":\n" + files.join(" ")) // Print the "ls" output
    for (file : files) {                            // Go through all the files in the directory
        if (file.isDirectory()) {                   // Check if the current file being looked at is a directory
            recursivelyList(directory)              // If so, recursively list that directory
        }
    }
}

Ve yapabildiğim için, aynı bir Java uygulaması .


23

Aslında, sormakta olduğunuz birbiriyle yakından ilişkili iki soru var.

  • Neden bir dosya sistemi hiyerarşisindeki her bir girdiye yürüme süreci doğal olarak özyinelemeli bir süreçtir? Bu, Zanna ve Kaz Wolfe gibi diğer cevaplarla ele alınmaktadır .
  • Uygulamada özyineleme tekniği nasıl kullanılır ls? İfadelerinizden ("Süreçte özyineleme nasıl kullanılır?"), Bence bu bilmek istediğiniz şeyin bir parçası. Bu cevap, bu soruyu ele almaktadır.

lsÖzyinelemeli bir teknikle uygulanması neden mantıklı :

FOLDOC özyinelemeyi şu şekilde tanımlar :

Bir işlev (veya prosedür ) kendisini çağırdığında. Böyle bir fonksiyon "özyinelemeli" olarak adlandırılır. Eğer çağrı bir veya daha fazla başka fonksiyonla yapılırsa, bu fonksiyon grubuna "karşılıklı özyinelemeli" denir.

Uygulamanın doğal yolu, lsgörüntülenecek dosya sistemi girişlerinin bir listesini oluşturan bir fonksiyon yazmak ve yolu ve seçenek argümanlarını işlemek ve girişleri istenen şekilde görüntülemek için diğer kodları yazmaktır. Bu fonksiyonun yinelemeli olarak uygulanması muhtemeldir.

Seçenek işleme sırasında, lsözyinelemeyle çalışması istenip istenmediğini belirleyecektir ( -Rbayrakla çağrılarak). Eğer öyleyse, girişlerin bir listesini oluşturur fonksiyonu dışında her dizin için bir kez listeleri kendisini arayacak görüntülenecek .ve ... Bu fonksiyonun özyinelemeli ve özyinemeli olmayan versiyonları olabilir veya özyinelemeli çalışması gerekiyorsa, fonksiyon her seferinde kontrol edebilir.

/bin/lsÇalıştırırken çalışan yürütülebilir dosya olan Ubuntu's , GNU Coreutilsls tarafından sağlanmaktadır ve birçok özelliğe sahiptir. Sonuç olarak, kodu beklediğinizden biraz daha uzun ve daha karmaşık. Ancak Ubuntu , BusyBox tarafından sağlanan daha basit bir sürümünü de içerir . Bunu yazarak çalıştırabilirsiniz .lsbusybox ls

busybox lsÖzyineleme nasıl kullanılır:

lsBusyBox içinde uygulanır coreutils/ls.c. scan_and_display_dirs_recur()Bir dizin ağacını yinelemeli olarak yazdırmak için çağrılan bir işlevi içerir :

static void scan_and_display_dirs_recur(struct dnode **dn, int first)
{
    unsigned nfiles;
    struct dnode **subdnp;

    for (; *dn; dn++) {
        if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
            if (!first)
                bb_putchar('\n');
            first = 0;
            printf("%s:\n", (*dn)->fullname);
        }
        subdnp = scan_one_dir((*dn)->fullname, &nfiles);
#if ENABLE_DESKTOP
        if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
            printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
#endif
        if (nfiles > 0) {
            /* list all files at this level */
            sort_and_display_files(subdnp, nfiles);

            if (ENABLE_FEATURE_LS_RECURSIVE
             && (G.all_fmt & DISP_RECURSIVE)
            ) {
                struct dnode **dnd;
                unsigned dndirs;
                /* recursive - list the sub-dirs */
                dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
                dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
                if (dndirs > 0) {
                    dnsort(dnd, dndirs);
                    scan_and_display_dirs_recur(dnd, 0);
                    /* free the array of dnode pointers to the dirs */
                    free(dnd);
                }
            }
            /* free the dnodes and the fullname mem */
            dfree(subdnp);
        }
    }
}

Özyinelemeli işlev çağrısının gerçekleştiği çizgi:

                    scan_and_display_dirs_recur(dnd, 0);

Özyinelemeli işlevi görüldüğü gibi çağırır:

busybox lsBir hata ayıklayıcıda çalıştırıyorsanız , bunu işlem sırasında görebilirsiniz . İlk yüklemek ayıklama simgeleri ile -dbgsym.ddeb paketleri sağlayan ve daha sonra yükleme busybox-static-dbgsympaketi. Ayrıca yükleyin gdb(bu hata ayıklayıcı).

sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym

coreutils lsBasit bir dizin ağacında hata ayıklamayı öneririm .

Eğer bir tane kullanışlı değilseniz, bir tane yapın (bu WinEunuuchs2Unix'in cevabındakimkdir -p komutla aynı şekilde çalışır ):

mkdir -pv foo/{bar/foobar,baz/quux}

Ve bazı dosyalarla doldurun:

(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)

busybox ls -R fooÇalışmaları beklendiği gibi doğrulayarak bu çıktıyı üretebilirsiniz:

foo:
bar    baz    file0

foo/bar:
file1   foobar

foo/bar/foobar:
file2

foo/baz:
file3  quux

foo/baz/quux:
file4

busyboxHata ayıklayıcısında aç :

gdb busybox

GDB kendisi hakkında bazı bilgiler yazdıracak. O zaman şöyle bir şey demeli:

Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)

(gdb)hata ayıklayıcınızdaki isteminiz nedir. GDB'ye bu komutta yapmasını söyleyeceğiniz ilk şey, scan_and_display_dirs_recur()fonksiyonun başlangıcında bir kesme noktası belirlemektir :

b scan_and_display_dirs_recur

Bunu çalıştırdığınızda, GDB size şöyle bir şey söylemeli:

Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.

Şimdi çalıştırmak için GDB anlatmak busyboxile bağımsız değişkenler olarak (istediğiniz ya da her türlü dizin adı):ls -R foo

run ls -R foo

Bunun gibi bir şey görebilirsiniz:

Starting program: /bin/busybox ls -R foo

Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6c60, first=1) at coreutils/ls.c:1026
1026    coreutils/ls.c: No such file or directory.

Gördüğün No such file or directorygibi, yukarıdaki gibi, sorun değil. Bu gösterimin amacı sadece scan_and_display_dirs_recur()fonksiyonun ne zaman çağrıldığını görmek, dolayısıyla GDB'nin gerçek kaynak kodunu incelemesi gerekmiyor.

Hata ayıklayıcının, herhangi bir dizin girişi yazdırılmadan önce bile kesme noktasına ulaştığına dikkat edin. Bu işleve girişte kopar , ancak bu işlevdeki kodun yazdırılması için herhangi bir dizin numaralandırması için çalışması gerekir.

GDB'ye devam etmesini söylemek için, şunları çalıştırın:

c

Her defasında scan_and_display_dirs_recur()aranır, kesme noktası tekrar vurulur, böylece tekrarı hareket halinde görürsünüz. Buna benziyor ( (gdb)istemi ve komutları dahil):

(gdb) c
Continuing.
foo:
bar    baz    file0

Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cb0, first=first@entry=0) at coreutils/ls.c:1026
1026    in coreutils/ls.c
(gdb) c
Continuing.

foo/bar:
file1   foobar

Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cf0, first=first@entry=0) at coreutils/ls.c:1026
1026    in coreutils/ls.c
(gdb) c
Continuing.

foo/bar/foobar:
file2

foo/baz:
file3  quux

Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cf0, first=first@entry=0) at coreutils/ls.c:1026
1026    in coreutils/ls.c
(gdb) c
Continuing.

foo/baz/quux:
file4
[Inferior 1 (process 2321) exited normally]

İşlev recuradında ... BusyBox yalnızca -Rbayrak verildiğinde kullanıyor mu? Hata ayıklayıcısında bunu bulmak kolaydır:

(gdb) run ls foo
Starting program: /bin/busybox ls foo

Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6c60, first=1) at coreutils/ls.c:1026
1026    in coreutils/ls.c
(gdb) c
Continuing.
bar    baz    file0
[Inferior 1 (process 2327) exited normally]

Olmasa da -R, bu özel uygulama uygulaması, lshangi dosya sistemi girişlerinin var olduğunu bulmak ve göstermek için aynı işlevi kullanır.

Hata ayıklayıcısını bırakmak istediğinizde, sadece söyle:

q

scan_and_display_dirs_recur()Kendisini araması gerektiğini nasıl bilebilir:

-RBayrak gönderildiğinde özellikle nasıl farklı işler ? Kaynak kodu incelemek (Ubuntu sisteminizdeki tam sürüm olmayabilir) G.all_fmt, hangi seçeneklerin çağrıldığını depoladığı dahili veri yapısını kontrol ettiğini gösterir :

            if (ENABLE_FEATURE_LS_RECURSIVE
             && (G.all_fmt & DISP_RECURSIVE)

(BusyBox desteği olmadan derlenmişse -R, dosya sistemi girişlerini yinelemeli olarak görüntülemeye çalışmaz; ENABLE_FEATURE_LS_RECURSIVEbölüm budur.)

G.all_fmt & DISP_RECURSIVEÖzyinelemeli işlev çağrısını içeren kod yalnızca doğru olduğunda çalışır.

                struct dnode **dnd;
                unsigned dndirs;
                /* recursive - list the sub-dirs */
                dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
                dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
                if (dndirs > 0) {
                    dnsort(dnd, dndirs);
                    scan_and_display_dirs_recur(dnd, 0);
                    /* free the array of dnode pointers to the dirs */
                    free(dnd);
                }

Aksi takdirde, işlev yalnızca bir kez çalışır (komut satırında belirtilen dizin başına).


Bir kez daha, Eliah aşırı kapsamlı bir cevapla geliyor. Hak ettiği +1.
Kaz Wolfe,

2
Oh, bu yüzden kuyruk özyineleme bile değil. Bu, yığın taşması nedeniyle meşgul kutusunun çökmesine neden olacak listeleme olan bazı dizin içerikleri olduğu anlamına gelmelidir (çok derin bir yuvalama olmasına rağmen).
Ruslan

2
Bu şaşırtıcı. Temelde OP'e hata ayıklama konusunda hızlı bir ders verdiniz, böylece işin nasıl yürüdüğünü tam olarak anlayabiliyorlar. Süper.
Andrea Lazzarotto

16

Bunu düşündüğünüzde, "özyinelemeli" dizinlerde ve bunların dosyalarında ve dizinlerinde ve dosyalarında ve dizinlerinde ve dosyalarında ve dosyalarında ve dosyalarında hareket eden komutları mantıklı kılar.

.... belirtilen noktadan aşağıya kadar olan tüm ağaç komut tarafından çalıştırılana kadar, bu durumda, altında bulunan tüm alt dizinlerin alt dizinlerinin içeriğini listeleyen ........... komutun argümanları


7

-R, gevşekçe "tekrar tekrar" olarak adlandırılabilecek özyineleme içindir.

Örneğin bu kodu alın:

───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/a
───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/b/1
───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/c/1/2
───────────────────────────────────────────────────────────────────────────────
$ ls -R temp
temp:
a  b  c

temp/a:

temp/b:
1

temp/b/1:

temp/c:
1

temp/c/1:
2

temp/c/1/2:

-pDizinleri yapımında kitle tek komutla dizinleri oluşturmanıza olanak sağlar. Üst-orta dizinlerden biri veya daha fazlası zaten mevcutsa, bu bir hata değildir ve orta-alt dizinler oluşturulur.

Ardından ls -Rtemp ile başlayan ve ağacın altından bütün dallara kadar çalışan her bir dizini özyinelemeli olarak listeler.

Şimdi ls -Rkomuta bir tamamlayıcıya bakalım , yani treekomut:

$ tree temp
temp
├── a
├── b
│   └── 1
└── c
    └── 1
        └── 2

6 directories, 0 files

Gördüğünüz gibi treeaynısını ls -Rbaşarırsak daha özlü ve cüretkar demeye "daha güzel" diyorum.

Şimdi yeni oluşturduğumuz dizinleri tek bir basit komutla tekrar tekrar nasıl kaldıracağınıza bakalım:

$ rm -r temp

Bu özyinelemeli tempve altındaki tüm alt dizinleri kaldırır . yani temp/a, temp/b/1ve temp/c/1/2ayrıca aradaki ortadaki dizinleri.


Eğer "ls -R" tekrar tekrar bir şeyler yapacak olsaydı, aynı çıktıyı birden çok defa alırsınız;) treeYine de + 1 . Harika bir araç.
Pod

Evet, Layman'ın sözünün fakir sesi. Programcı olmayan türlerin daha kolay anlaşılmasını sağlamak için ana akımda bir kelime bulmaya çalışıyordum. Daha sonra daha iyi bir kelime düşünmeye veya silmeye çalışacağım.
WinEunuuchs2Unix

5

İşte basit bir açıklama, mantıklı çünkü alt dizinlerin içeriğini görüntülemek söz konusu olduğunda, aynı fonksiyon zaten bir dizin ile ne yapılacağını biliyor. Bu nedenle, bu sonucu almak için her bir alt dizinde kendisini çağırması yeterlidir!

Sahte kodda şöyle görünür:

recursive_ls(dir)
    print(files and directories)
    foreach (directoriy in dir)
        recursive_ls(directory)
    end foreach
end recursive_ls
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.