Ich möchte wissen, wie viele reguläre Dateien die Erweiterung .c
in einer großen komplexen Verzeichnisstruktur haben und wie viele Verzeichnisse sich auf diese Dateien verteilen. Die Ausgabe, die ich möchte, sind nur diese beiden Zahlen.
Ich habe diese Frage zum Abrufen der Anzahl der Dateien gesehen, muss aber auch die Anzahl der Verzeichnisse kennen, in denen sich die Dateien befinden.
- Meine Dateinamen (einschließlich der Verzeichnisse) können beliebige Zeichen enthalten. Sie können mit
.
oder beginnen-
und Leerzeichen oder Zeilenumbrüche enthalten. - Ich könnte einige Symlinks haben, deren Namen mit
.c
und Symlinks zu Verzeichnissen enden . Ich möchte nicht, dass Symlinks verfolgt oder gezählt werden, oder ich möchte zumindest wissen, ob und wann sie gezählt werden. - Die Verzeichnisstruktur besteht aus mehreren Ebenen und das oberste Verzeichnis (das Arbeitsverzeichnis) enthält mindestens eine
.c
Datei.
Ich habe einige Befehle hastig in die (Bash-) Shell geschrieben, um sie selbst zu zählen, aber ich glaube nicht, dass das Ergebnis korrekt ist ...
shopt -s dotglob
shopt -s globstar
mkdir out
for d in **/; do
find "$d" -maxdepth 1 -type f -name "*.c" >> out/$(basename "$d")
done
ls -1Aq out | wc -l
cat out/* | wc -l
Dies gibt Beschwerden über mehrdeutige Weiterleitungen aus, verpasst Dateien im aktuellen Verzeichnis und stößt auf Sonderzeichen (bei einer umgeleiteten find
Ausgabe werden beispielsweise Zeilenumbrüche in Dateinamen gedruckt ) und schreibt eine ganze Reihe leerer Dateien (oops).
Wie kann ich meine .c
Dateien und die darin enthaltenen Verzeichnisse zuverlässig auflisten ?
Falls es hilft, sind hier einige Befehle zum Erstellen einer Teststruktur mit falschen Namen und Symlinks:
mkdir -p cfiles/{1..3}/{a..b} && cd cfiles
mkdir space\ d
touch -- i.c -.c bad\ .c 'terrible
.c' not-c .hidden.c
for d in space\ d 1 2 2/{a..b} 3/b; do cp -t "$d" -- *.c; done
ln -s 2 dirlink
ln -s 3/b/i.c filelink.c
In der resultierenden Struktur enthalten 7 Verzeichnisse .c
Dateien und 29 reguläre Dateien enden mit .c
(wenn dotglob
deaktiviert, wenn die Befehle ausgeführt werden) (wenn ich falsch gezählt habe, lassen Sie es mich bitte wissen). Das sind die Zahlen, die ich will.
Fühlen Sie sich bitte frei , diesen speziellen Test nicht zu verwenden.
NB: Antworten in einer beliebigen Shell oder einer anderen Sprache werden von mir getestet und geschätzt. Wenn ich neue Pakete installieren muss, kein Problem. Wenn Sie eine GUI-Lösung kennen, empfehle ich Ihnen, sie zu teilen (aber ich gehe möglicherweise nicht so weit, um eine ganze DE zu installieren, um sie zu testen) :) Ich verwende Ubuntu MATE 17.10.
Antworten:
Ich habe die Ausgabe nicht mit Symlinks untersucht, aber:
find
Befehl gibt den Verzeichnisnamen jeder gefundenen.c
Datei aus.sort | uniq -c
will gibt an, wie viele Dateien sich in jedem Verzeichnis befinden (das ist hiersort
möglicherweise unnötig, nicht sicher)sed
ersetze ich den Verzeichnisnamen durch1
und eliminiere so alle möglichen seltsamen Zeichen, nur die Anzahl und die1
verbleibendentr
d
im Wesentlichen dasselbe ist wieNR
. Ich hätte auf das Einfügen1
dessed
Befehls verzichten und nurNR
hier drucken können , aber ich denke, das ist etwas klarer.Bis dahin
tr
sind die Daten NUL-getrennt und gegen alle gültigen Dateinamen geschützt.Mit zsh und bash können Sie
printf %q
einen String in Anführungszeichen setzen, der keine Zeilenumbrüche enthält. Sie könnten also in der Lage sein, Folgendes zu tun:Obwohl
**
Symlinks zu Verzeichnissen nicht erweitert werden sollen , konnte ich auf bash 4.4.18 (1) (Ubuntu 16.04) nicht die gewünschte Ausgabe erhalten.Aber zsh hat gut funktioniert und der Befehl kann vereinfacht werden:
D
aktiviert dieses Glob, um Punktdateien auszuwählen,.
wählt reguläre Dateien (also keine Symlinks) aus und:h
druckt nur den Verzeichnispfad und nicht den Dateinamen (wiefind
's%h
) (siehe Abschnitte über die Generierung von Dateinamen und Modifikatoren ). Mit dem Befehl awk müssen wir also nur die Anzahl der angezeigten eindeutigen Verzeichnisse und die Anzahl der Zeilen die Anzahl der Dateien zählen.quelle
29 7
. Wenn ich hinzufügen-L
zufind
, geht das bis zu41 10
. Welchen Output brauchst du?Python hat
os.walk
, was Aufgaben wie diese einfach, intuitiv und automatisch robust macht, selbst angesichts seltsamer Dateinamen wie denen, die Zeilenumbrüche enthalten. Dieses Python 3-Skript, das ich ursprünglich im Chat gepostet hatte , soll im aktuellen Verzeichnis ausgeführt werden ( es muss sich jedoch nicht im aktuellen Verzeichnis befinden und Sie können den Pfad ändern, an den es übergeben wirdos.walk
):Das gibt die Anzahl der Verzeichnisse aus, die direkt mindestens eine Datei enthalten, deren Name auf endet
.c
, gefolgt von einem Leerzeichen, gefolgt von der Anzahl der Dateien, deren Namen auf endet.c
. "Versteckte" Dateien,.
dh Dateien, deren Namen mit "-" beginnen, werden eingeschlossen, und versteckte Verzeichnisse werden auf ähnliche Weise durchsucht.os.walk
rekursiv durchläuft eine Verzeichnishierarchie. Es listet alle Verzeichnisse auf, auf die rekursiv von dem angegebenen Ausgangspunkt aus zugegriffen werden kann, und gibt Informationen zu jedem dieser Verzeichnisse als Tupel mit drei Werten ausroot, dirs, files
. Für jedes Verzeichnis, in das es übergeht (einschließlich des ersten Verzeichnisses, dessen Namen Sie ihm geben):root
Enthält den Pfadnamen dieses Verzeichnisses. Beachten Sie, dass dies völlig unabhängig von dem System der „Wurzelverzeichnis“/
(und auch in keinem Zusammenhang mit/root
) , obwohl es würde zu denen gehen , wenn Sie beginnen dort. In diesem Fallroot
beginnt der Pfad -.
dh das aktuelle Verzeichnis - und verläuft überall darunter.dirs
Enthält eine Liste der Pfadnamen aller Unterverzeichnisse des Verzeichnisses, in dem sich der Name derzeit befindetroot
.files
Enthält eine Liste der Pfadnamen aller Dateien , die sich in dem Verzeichnis befinden, in dem der Name derzeit gespeichert ist , dieroot
jedoch keine eigenen Verzeichnisse sind. Beachten Sie, dass dies andere Dateitypen als normale Dateien umfasst, einschließlich symbolischer Links. Es scheint jedoch, dass Sie nicht erwarten, dass solche Einträge auf enden,.c
und dass Sie daran interessiert sind, welche zu sehen.In diesem Fall muss ich nur das dritte Element des Tupels untersuchen
files
(das ichfs
im Skript aufrufe). Wie derfind
Befehl wechselt Pythonos.walk
für mich in Unterverzeichnisse. Das einzige, was ich mir ansehen muss, sind die Namen der Dateien, die jede von ihnen enthält. Im Gegensatz zumfind
Befehl wirdos.walk
mir jedoch automatisch eine Liste dieser Dateinamen zur Verfügung gestellt.Dieses Skript folgt keinen symbolischen Links. Sehr wahrscheinlich möchten Sie nicht, dass Symlinks für eine solche Operation befolgt werden, da sie Zyklen bilden können und auch wenn es keine Zyklen gibt, dieselben Dateien und Verzeichnisse möglicherweise mehrfach durchlaufen und gezählt werden, wenn auf sie über verschiedene Symlinks zugegriffen werden kann.
Wenn Sie
os.walk
Symlinks folgen wollten - was normalerweise nicht der Fall ist -, können Sie diese weiterleitenfollowlinks=true
. Das heißt, anstatt zu schreiben,os.walk('.')
könntest du schreibenos.walk('.', followlinks=true)
. Ich wiederhole, dass Sie das selten wollen, besonders bei einer Aufgabe wie dieser, bei der Sie eine ganze Verzeichnisstruktur rekursiv aufzählen, egal wie groß sie ist, und alle Dateien darin zählen, die eine bestimmte Anforderung erfüllen.quelle
Find + Perl:
Erläuterung
Der
find
Befehl findet alle regulären Dateien (also keine Symlinks oder Verzeichnisse) und gibt dann den Namen des Verzeichnisses aus, in dem sie sich befinden (%h
), gefolgt von\0
.perl -0 -ne
: Lies die Eingabe Zeile für Zeile (-n
) und wende das Skript von-e
auf jede Zeile an. Das-0
setzt das Eingabezeilentrennzeichen auf,\0
damit durch Nullen getrennte Eingaben gelesen werden können.$k{$_}++
:$_
ist eine spezielle Variable, die den Wert der aktuellen Zeile annimmt. Dies wird als Schlüssel für den Hash verwendet%k
, dessen Werte die Häufigkeit angeben , mit der jede Eingabezeile (Verzeichnisname) gesehen wurde.}{
: Dies ist eine Kurzform des SchreibensEND{}
. Alle Befehle nach dem}{
werden einmal ausgeführt, nachdem alle Eingaben verarbeitet wurden.print scalar keys %k, " $.\n"
:keys %k
gibt ein Array der Schlüssel im Hash zurück%k
.scalar keys %k
gibt die Anzahl der Elemente in diesem Array und die Anzahl der angezeigten Verzeichnisse an. Dies wird zusammen mit dem aktuellen Wert$.
einer speziellen Variablen gedruckt , die die aktuelle Eingabezeilennummer enthält. Da dies am Ende ausgeführt wird, ist die aktuelle Eingabezeilennummer die Nummer der letzten Zeile, also die Anzahl der bisher gesehenen Zeilen.Sie können den Perl-Befehl aus Gründen der Übersichtlichkeit folgendermaßen erweitern:
quelle
Hier ist mein Vorschlag:
Dieses kurze Skript erstellt ein tempfile, findet jede Datei in und unter dem aktuellen Verzeichnis
.c
und schreibt die Liste in das tempfile.grep
wird dann verwendet, um die Dateien zweimal zu zählen (wie folgt: Wie kann ich die Anzahl der Dateien in einem Verzeichnis über die Befehlszeile ermitteln? ): Beim zweiten Mal werden Verzeichnisse, die mehrfach aufgeführt sind, mit entfernt,sort -u
nachdem mit Dateinamen aus jeder Zeile entfernt wurdensed
.Dies funktioniert auch ordnungsgemäß mit Zeilenumbrüchen in Dateinamen:
grep -c /
Zählt nur Zeilen mit einem Schrägstrich und berücksichtigt daher nur die erste Zeile eines mehrzeiligen Dateinamens in der Liste.Ausgabe
quelle
Kleines Muschelskript
Ich schlage ein kleines Bash-Shellscript mit zwei Hauptbefehlszeilen vor (und einer Variablen
filetype
, die das Umschalten erleichtert, um nach anderen Dateitypen zu suchen).Es wird nicht nach oder in Symlinks gesucht, sondern nur nach regulären Dateien.
Ausführliches Shellscript
Dies ist eine ausführlichere Version, die auch symbolische Links berücksichtigt,
Ausgang testen
Aus dem kurzen Muschelskript:
Aus dem ausführlichen Shellscript:
quelle
Einfacher Perl One Liner:
Oder einfacher mit
find
Befehl:Wenn Sie gerne Golf spielen und aktuelles (weniger als zehn Jahre altes) Perl haben:
quelle
Verwenden Sie den
locate
Befehl, der viel schneller ist als derfind
Befehl.Läuft auf Testdaten
Vielen Dank an Muru für seine Antwort, die mir dabei hilft, symbolische Links aus der Dateizählung in Unix & Linux zu entfernen .
Danke an Terdon für die Antwort von
$PWD
(nicht an mich gerichtet) in Unix & Linux .Ursprüngliche Antwort unten durch Kommentare verwiesen
Kurzform:
sudo updatedb
Aktualisieren Sie die vomlocate
Befehl verwendete Datenbank, wenn heute.c
Dateien erstellt wurden oder wenn Sie.c
heute Dateien gelöscht haben.locate -cr "$PWD.*\.c$"
Suchen Sie alle.c
Dateien im aktuellen Verzeichnis und dessen untergeordneten Dateien ($PWD
). Anstatt Dateinamen auszudrucken, und zählen Sie mit-c
Argument. Dasr
spezifiziert reguläre Ausdrücke anstelle der Standardübereinstimmung*pattern*
, was zu vielen Ergebnissen führen kann.locate -r "$PWD.*\.c$" | sed 's%/[^/]*$%/%' | uniq -c | wc -l
. Suchen Sie alle*.c
Dateien im aktuellen Verzeichnis und darunter. Entfernen Sie den Dateinamen,sed
indem Sie nur den Verzeichnisnamen belassen. Zählen Sie die Anzahl der Dateien in jedem Verzeichnis mituniq -c
. Anzahl der Verzeichnisse mit zählenwc -l
.Beginnen Sie im aktuellen Verzeichnis mit einem Zeilenumbruch
Beachten Sie, wie sich die Anzahl der Dateien und Verzeichnisse geändert hat. Ich glaube, dass alle Benutzer das
/usr/src
Verzeichnis haben und die obigen Befehle mit unterschiedlicher Anzahl ausführen können, abhängig von der Anzahl der installierten Kernel.Lange Form:
Die lange Form enthält die Zeit, sodass Sie sehen können, wie viel schneller vorbei
locate
istfind
. Auch wenn Sie laufen müssensudo updatedb
, ist es um ein Vielfaches schneller als ein einzelnesfind /
.Hinweis: Dies sind alle Dateien auf ALLEN Laufwerken und Partitionen. Dh wir können auch nach Windows Befehlen suchen:
Ich habe drei Windows 10 NTFS-Partitionen automatisch eingehängt
/etc/fstab
. Seien Sie sich bewusst, locate weiß alles!Interessante Anzahl:
Es dauert 15 Sekunden, um 1.637.135 Dateien in 286.705 Verzeichnissen zu zählen. YMMV.
Für eine detaillierte Aufschlüsselung
locate
der regulären Ausdrücke des Befehls (wird in dieser Frage und Antwort nicht benötigt, wird aber nur für den Fall verwendet) lesen Sie bitte Folgendes: Verwenden Sie "locate" unter einem bestimmten Verzeichnis?Zusätzliche Lektüre aus neueren Artikeln:
quelle
.c
(beachten Sie, dass dies nicht funktioniert, wenn eine Datei-.c
im aktuellen Verzeichnis enthalten ist, da Sie keine Anführungszeichen setzen*.c
). Anschließend werden alle Verzeichnisse gedruckt im System, unabhängig davon, ob sie .c-Dateien enthalten.~/my_c_progs/*.c
. Es werden 638 Verzeichnisse mit.c
Programmen gezählt, die Gesamtzahl der Verzeichnisse wird später als angezeigt286,705
. Ich werde die Antwort in Anführungszeichen "* .c" ändern. Danke für den Tipp.locate -r "/path/to/dir/.*\.c$"
, aber das wird in Ihrer Antwort nirgendwo erwähnt. Sie geben nur einen Link zu einer anderen Antwort, in der dies erwähnt wird, ohne jedoch zu erklären, wie Sie diese anpassen müssen, um die hier gestellte Frage zu beantworten. Ihre gesamte Antwort konzentriert sich darauf, wie die Gesamtzahl der Dateien und Verzeichnisse auf dem System gezählt wird, was für die Frage "Wie kann ich die Anzahl der .c-Dateien und die Anzahl der enthaltenen Verzeichnisse zählen?" Nicht relevant ist. c Dateien in einem bestimmten Verzeichnis ". Auch Ihre Nummern stimmen nicht, probieren Sie es am Beispiel im OP.$PWD
Variable gepostet haben : unix.stackexchange.com/a/188191/200094$PWD
keine Zeichen enthält , die vielleicht besondere in einem regex