Anonymous
Anonymous asked in Computers & InternetProgramming & Design · 3 years ago

[Linux] How can I make a program that counts files in directory?

Like if the user writes "count /bin", it counts the number of files, folders and symbolic links, and if you add an option like "count -f /bin" it counts files only and so forth?

3 Answers

Relevance
  • oyubir
    Lv 6
    3 years ago
    Favorite Answer

    Chris answer (roughly "ls -1 | wc -l") is correct for the first question. It counts everything (files, directories, symlinks, block devices, char devices, named pipes, named sockets, special files, etc)

    Now, if you want to filter, that is a little bid harder.

    One solution, in the spirit of that answer, would be to use -F option of ls, and then filter with grep, before counting

    Number of directories:

    ls -1 -F | grep "/$" | wc -l

    Number of symbolic links:

    ls -1 -F | grep "@$" | wc -l

    Number of pipes:

    ls -1 -F | grep "|$" | wc -l

    Number of sockets:

    ls -1 -F | grep "=$" | wc -l

    There is no indicator in -F output for regular files. You can detect the absence of indicator

    ls -1 -F | grep "[^=/@|]$" | wc -l

    But theoretically, that is not correct. Since nothing prevent a regular file to actually end with one of those indicator (except the /, which is forbidden). So you if you do it like this, you are relying on the fact that nobody would ever create, in one of the directories you are interested in, a file whose name ends with an indicator of ls -F

    If you want to also count hidden files (starting with a point), you have to add option "-A" to all those "ls".

    For example, for regular files:

    ls -A -1 -F | grep "[^=/@|]$" | wc -l

    Another, more reliable, way to do it, is to use find.

    Find is recursive (it list all files in a given directory, and in its subdirectories, and subdirectories of subdirectories, etc)

    But with option "-maxdept 1", find will not explore anything under the first level.

    So it is almost the same. Except that find count the directory (/bin in your example), so you have to remove 1 to the result for files)

    find /bin -maxdepth 1 | wc -l

    counts the number of files in /bin, "/bin" included. So remove 1 for what you want.

    Then, you can ask find to filter using many of find options (see "man find" for details) depending on a bunch of criteria, among which file type.

    For example

    find /bin/ -maxdepth 1 -type f | wc -l

    counts all regular files under /bin (this time, no need to substract 1, since /bin is not a regular file)

    Directories:

    find /bin/ -maxdepth 1 -type d | wc -l

    (there you must substract 1, since /bin/ is a directory)

    Symbolic links:

    find /bin/ -maxdepth 1 -type l | wc -l

    Last boring remark: theoretically, you could have a carriage return in a filename. Current implementation of find on my linux distribution replaces them with "?", so that would not change the count. But some other might really print the "\n" (last time I tried, it was doing so).

    So, theoretically, those commands might count twice a file whose name contains a "carriage return".

    So, you can instead, since, after all, you don't really need find to display the actual file names, use this command (you just want find to display one thing per file, and then count the number of printed things. Whether that thing is the file name, or something else doesn't matter):

    find /bin/ -type f -printf "x" | wc -c

    (find displays a "x" for every regular file in /bin/. And wc -c counts the number of x. Sure, that usage of printf is quite strange. Printf is mainly made to print interesting data, like find -printf "%f %a\n" which prints all files with their last access time. But after all, why not just printing "x"?)

    Which is my final and best answer, I guess.

    find DIRECTORY -maxdepth 1-type TYPE -printf x | wc -c

    In a script, named count, that would be:

    find "$2" -maxdepth 1 -type $1 -printf x | wc -c

    If you insist on the option (first arg) to be "-f", "-d", ... instead of just "f" "d"...

    find "$2" -maxdepth 1 -type ${1##-} -printf x | wc -c

    ${x##-} is x without "-" prefix

    cnt=$(find "$2" -maxdepth 1 -type ${1##-} -printf x | wc -c)

    [[ "$1" = "-d" ]] && ((cnt--))

    echo $cnt

    In one line, just for fun, and obfuscation:

    echo $(( $(find "$2" -maxdepth 1 -type ${1##-} -printf x | wc -c) - $(find "$2" -maxdepth 0 -type ${1##-} -printf x | wc -c) ))

    (With, also for fun, another way to deal with the "-d" case: instead of substracting 1 for the directory, we substract the result of the same find with maxdepth 0. This result will be 1 in the case of -d. And 0 else. Advantage of doing so: it will works even in strange cases, like if "/bin" is a symlink itself)

    So, final answer:

    echo $(( $(find "$2" -maxdepth 1 -type ${1##-} -printf x | wc -c) - $(find "$2" -maxdepth 0 -type ${1##-} -printf x | wc -c) ))

    Just one line. And it works with any strange cases you could thing of (files with indicators, or carriage returns in it, etc)

    • Commenter avatarLogin to reply the answers
  • Andy T
    Lv 7
    3 years ago

    If you did go with the ls/grep BASH route you need

    #/bin/bash

    at beginning and chmod +x file.sh

    • Commenter avatarLogin to reply the answers
  • Chris
    Lv 7
    3 years ago
    • Commenter avatarLogin to reply the answers
Still have questions? Get your answers by asking now.