Home > Back-end >  How to get file count and names in directory on bash
How to get file count and names in directory on bash

Time:07-12

I want to get the file count & file names & folder names in directory:

mkdir -p /tmp/test/folder1
mkdir -p /tmp/test/folder2
touch /tmp/test/file1
touch /tmp/test/file2

file_names=$(find "/tmp/test" -mindepth 1 -maxdepth 1 -type f -print0 | xargs -0 -I {} basename "{}")
echo $file_names

here is the output:

file2 file1

For folder:

folder_names=$(find "/tmp/test" -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -I {} basename "{}")
echo $folder_names

here is the output:

folder2 folder1

For count:

file_count=0 && $(find "/tmp/test" -mindepth 1 -maxdepth 1 -type f -print0 | let "file_count=file_count 1")
echo $file_count

folder_count=0 && $(find "/tmp/test" -mindepth 1 -maxdepth 1 -type d -print0 | let "folder_count=folder_count 1")
echo $folder_count

The file_count and folder_count does not work

Question 1:

How to get the correct file_count and folder_count?

Question 2:

Is it possible for getting names into an array and check the count from array size?

CodePudding user response:

The answer to the second question is really the answer to the first, too.

mapfile -d '' files < <( find /tmp/test -type f \
    -mindepth 1 -maxdepth 1 \
    -printf '%f\0')
echo "${#files} files"
printf '%s\n' "${files[@]}"

The use of double quotes and @ in the array expansion are essential for printing file names with whitespace correctly. The use of a null byte terminator between file names ensures that even newlines in file names are disambiguated.

Notice also the use of -printf with a specific format string to avoid having to run basename separately. However, the -printf option and its various format strings, as well as the -print0 option you used, are a GNU find extension, and thus not portable. (Linux typically ships with GNU tools; on other platforms, they are obviously easy to install, but typically not installed out of the box.)

If you have an older version of Bash which doesn't support mapfiles, try an explicit loop:

files=()
while IFS= read -r -d $'\0' file; do
    files =("$file")
done < <(find ...)

If you don't have GNU find, a common workaround is to print a fixed string for each found file, and then the line or character count reliably reflects the number of found files.

find /tmp/test -type f \
  -mindepth 1 -maxdepth 1 \
  -exec printf . \; |
wc -c

Though then, how do you collect the file names? If (as in your case) you don't require recursion into subdirectories, simply loop over all items in the directory. In which case, again, the number of items in the collected array will also tell you how many there are.

files=()
dirs=()
for item in /tmp/test/*; do
    if [[ -f "$item"]]; then
        files =("$item")
    elif [[ -d "$item" ]]; then
        dirs =("$item")
    fi
done
echo "${#dirs[@] directories}
printf '- %s\n' "${dirs[@]}"
echo "${#files[@]} files"
printf '%s\n' "${dirs[@]}"

For a further discussion, see also https://mywiki.wooledge.org/BashFAQ/020

Needlessly collecting items into an array so you can loop over it once is a common beginner antipattern. If you just want to process each item in isolation and then forget it, there is no need to waste memory on remembering all the other items - just loop over them directly.

As an aside, running find in a subprocess will create a new shell instance with its own set of variables; thus in your attempt, the pipe to let would increment from 0 to 1 each time you ran it (though of course, piping to let also does not do anything useful anyway).

  • Related