Gibt es eine prägnantere Alternative zum Weiterleiten an wc zum Zählen von Dateien in einem Verzeichnis?

12

In diesem ls -1 target_dir | wc -lFall werden die Dateien in einem Verzeichnis gezählt. Das finde ich etwas umständlich. Gibt es einen eleganteren oder prägnanteren Weg?

Codecowboy
quelle
2
Sie brauchen das "-1" nicht, wenn Sie zu wc leiten.
Steve
lsgibt schon die Gesamtzahl an, also wie wäre es ls -l | head -1? Machen Sie es zu einem Alias, wenn Sie etwas kürzeres wollen.
Daniel Wagner
2
@DanielWagner Die Ausgabe von "total: nnn" ls -lgibt die Gesamtgröße der Dateien an, nicht die Anzahl der Dateien.
David Richerby
2
Denken Sie daran, ls | wc -ldass Sie die falsche Anzahl erhalten, wenn Dateinamen Zeilenumbrüche enthalten.
Chepner
Dies hängt vom Dateisystem ab und zählt Verzeichnisse + 2 in einem Verzeichnis. Die Antwort hat 2 extra (wie es sich selbst und seine Eltern zählt). stat -c %h .gibt die gleichen Informationen wiels -ld . | cut -d" " -f 2
Strg-Alt-Delor

Antworten:

12

Angenommen, bash 4+ (welches jede unterstützte Version von Ubuntu hat):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Nennen Sie es als num_files [dir]. dirist optional, ansonsten wird das aktuelle Verzeichnis verwendet. In Ihrer Originalversion werden ausgeblendete Dateien nicht berücksichtigt. Dies gilt auch nicht für diese. Wenn du das willst, shopt -s dotglobvorher set -- *.

Ihr ursprüngliches Beispiel enthält nicht nur reguläre Dateien, sondern auch Verzeichnisse und andere Geräte. Wenn Sie wirklich nur reguläre Dateien (einschließlich Symlinks zu regulären Dateien) möchten, müssen Sie diese überprüfen:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

Wenn Sie GNU find haben, ist so etwas auch eine Option (beachten Sie, dass dies versteckte Dateien enthält, die Ihr ursprünglicher Befehl nicht ausgeführt hat):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(Wechseln Sie -typezu, -xtypewenn Sie auch Symlinks zu regulären Dateien zählen möchten.)

Chris Down
quelle
Wird nicht setscheitern, wenn es sehr viele Dateien gibt? Ich denke, Sie müssen möglicherweise einen xargsSummierungscode verwenden, damit dies im allgemeinen Fall funktioniert.
l0b0
1
Auch shopt -s dotglobwenn Sie möchten, dass Dateien beginnend mit .gezählt werden
Digital Trauma
1
@ l0b0 Ich glaube nicht, dass setdas unter diesen Umständen scheitern wird, da wir eigentlich keine machen exec. Das heißt, auf meinem System getconf ARG_MAXergibt sich 262144, aber wenn ich das tue test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, antwortet es glücklich 262145.
Kojiro
@DavidRicherby -maxdepthist kein POSIX.
Chris Down
4
@MichaelMartinez Das Schreiben von offensichtlichem Code ist kein Ersatz für das Schreiben von korrektem Code.
Chris Down
3

f=(target_dir/*);echo ${#f[*]}

Funktioniert korrekt für Dateien mit Leerzeichen, Zeilenumbrüchen usw. im Namen.

Aaron Davies
quelle
Können Sie einen Kontext angeben? Sollte dies in einem Bash-Skript gehen?
Codecowboy
es könnte. Sie können es auch direkt in die Schale stecken. In dieser Version wurde davon ausgegangen, dass Sie das aktuelle Verzeichnis haben möchten. Ich habe es so bearbeitet, dass es Ihrer Frage näher kommt. Im Grunde erstellt es eine Shell-Array-Variable, die alle Dateien im Verzeichnis enthält, und gibt dann die Anzahl dieses Arrays aus. sollte in jeder Shell mit Arrays funktionieren - bash, ksh, zsh usw. - aber wahrscheinlich nicht schlicht sh / ash / dash.
Aaron Davies
2

lsist nur mehrspaltig, wenn es direkt an ein Terminal wcausgegeben wird , können Sie die Option "-1" entfernen, Sie können die Option "-l" entfernen , nur den ersten Wert lesen (träge Lösung, nicht für rechtliche Beweise zu verwenden, strafrechtliche Ermittlungen, missionskritische, taktische Operationen ..).

ls target | wc 
Emmanuel
quelle
5
Dies schlägt bei Dateinamen mit Zeilenumbrüchen fehl.
l0b0
@Emmanuel Sie müssen das Ergebnis Ihrer Analyse analysieren wc, um die Anzahl der Dateien in einem einfachen Fall zu ermitteln. Wie ist das überhaupt eine Lösung?
l0b0
@Emmanuel Dies kann fehlschlagen, wenn targetes sich um einen Glob handelt, der, wenn er erweitert wird, einige Dinge enthält, die mit Bindestrichen beginnen. touch -- {1,2,3,-a}.txt && ls *|wcrm -- *.txt
Erstellen
Meinten Sie wc -l? Andernfalls erhalten Sie Zeilenumbrüche, Wörter und Bytes für die lsAusgabe. Das hat David Richerby gesagt: Man muss es noch einmal analysieren.
Erik
@erik Ich nenne wckein Argument, das Sie nicht analysieren müssen, wenn Ihr Gehirn weiß, dass das erste Argument Newline ist.
Emmanuel
2

Wenn es Prägnanz sind Sie nach ( und nicht genauer Richtigkeit , wenn sie mit Dateien mit Zeilenumbrüchen in ihrem Namen zu tun, etc.), empfehle ich nur Aliasing wc -lzu lc( „Zeilenzahl“):

$ alias lc='wc -l'
$ ls target_dir|lc

Wie andere angemerkt haben, brauchen Sie diese -1Option nicht ls, da dies automatisch lsgeschieht, wenn in eine Pipe geschrieben wird. (Es sei denn, Sie haben sich darauf lsgeeinigt, immer den Spaltenmodus zu verwenden. Ich habe das schon einmal gesehen, aber nicht sehr oft.)

Ein lcAlias ​​ist im Allgemeinen sehr praktisch, und wenn Sie sich den Fall "count the current directory" ansehen, ls|lcist diese Frage so kurz wie möglich.

Aaron Davies
quelle
2

Bisher ist Aarons Ansatz der einzige, der prägnanter ist als Ihr eigener. Eine korrektere Version Ihres Ansatzes könnte folgendermaßen aussehen:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Das listet rekursiv alle Dateien auf - keine Verzeichnisse - eine pro Zeile, einschließlich .dot-Dateien unter dem aktuellen Verzeichnis, wobei Shell-Globs verwendet werden, um nicht druckbare Zeichen zu ersetzen. grep filtert alle übergeordneten Verzeichnislisten oder .. oder * / oder Leerzeilen aus. Es sollte also nur eine Zeile pro Datei vorhanden sein, deren Gesamtanzahl grep an Sie zurückgibt. Wenn Sie möchten, dass auch untergeordnete Verzeichnisse einbezogen werden, gehen Sie wie folgt vor:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Entfernen Sie -Rin beiden Fällen die Option, wenn Sie keine rekursiven Ergebnisse erhalten möchten.

mikeserv
quelle
1
Ich mache so etwas lieber mit find. Wenn Sie nur eine Zählung wünschen, sollte dies funktionieren: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(Entfernen Sie die Tiefensteuerelemente, um rekursive Ergebnisse zu erhalten).
Aaron Davies
@AaronDavies - das funktioniert eigentlich nicht. Fügen Sie in einen dieser Dateinamen einen Zeilenumbruch ein und überzeugen Sie sich selbst. Um dasselbe auch portabel zu machen, machen Sie: find . \! -name . -prune | wc -l- was natürlich immer noch nicht funktioniert.
mikeserv
1
Ich folge nicht - die printfAnweisung gibt eine konstante Zeichenfolge (eine neue Zeile) aus, die den Dateinamen überhaupt nicht enthält, sodass die Ergebnisse unabhängig von seltsamen Dateinamen sind. Dieser Trick ist mit einem findnicht unterstützten Trick printfnatürlich überhaupt nicht möglich .
Aaron Davies
@ AaronDavies - oh, wahr. Ich nahm an, dass der Dateiname enthalten war. Es kann aber natürlich auch portabel gemacht werden:find .//. \!. -name . -prune | grep -c '^\.//\.'
mikeserv
brillant! /Da die .//.Sequenz das einzige andere Zeichen ist, das nicht in Dateinamen vorkommt, wird sie garantiert für jede Datei genau einmal angezeigt, oder? Ein paar Fragen - warum .//.und warum -prune? Wann würde das anders sein find . \! -name . | grep -c '^\.'? (Ich nehme an, das .in Ihrem \!.ist ein Tippfehler.)
Aaron Davies