bash - count files per directory and total at the end - linux

Ubuntu 18.04 LTS with bash 4.4.20
I am trying to count the number of files in each directory starting in the directory where I executed the script. Borrowing from other coders, I found this script and modified it. I am trying to modify it to provide a total at the end, but I can't seem to get it. Also, the script is running the same count function twice each loop and that is inefficient. I inserted that extra find command because I could not get the results of the nested 'find | wc -l' to store in a variable. And it still didn't work.
Thanks!
#!/bin/bash
count=0
find . -maxdepth 1 -mindepth 1 -type d | sort -n | while read dir; do
printf "%-25.25s : " "$dir"
find "$dir" -type f | wc -l
filesthisdir=$(find "$dir" -type f | wc -l)
count=$count+$filesthisdir
done
echo "Total files : $count"
Here are the results. It should total up the results. Otherwise, this would work well.
./1800wls1 : 1086
./1800wls2 : 1154
./1900wls-in1 : 780
./1900wls-in2 : 395
./1900wls-in3 : 0
./1900wls-out1 : 8
./1900wls-out2 : 304
./1900wls-out3 : 160
./test : 0
Total files : 0

This doesn't work because the while loop is executed in a sub shell. By using <<< you make sure it's executed in the current shell.
#!/bin/bash
count=0
while read dir; do
printf "%-25.25s : " "$dir"
find "$dir" -type f | wc -l
filesthisdir=$(find "$dir" -type f | wc -l)
((count+=filesthisdir))
done <<< "$(find . -maxdepth 1 -mindepth 1 -type d | sort -n)"
echo "Total files : $count"
Of course you also can make use of a for loop:
for i in "$(find . -maxdepth 1 -mindepth 1 -type d | sort -n)"; do
# do something
done

Use (( count += filesthisdir)) and think about counting files with newlines.
You should change your find command:
filesthisdir=$(find "$dir" -type f -exec echo . \;| wc -l)

Related

echo the output of a ls command with less files than n

I have 400 folders with several files inside, I am interested in:
counting how many files with the extension .solution are in each folder, and
then output only those folder have less than 440 elements
The point 1) is easy to get with the command:
for folder in $(ls -d */ | grep "sol_cv_");
do
a=$(ls -1 "$folder"/*.solution | wc -l);
echo $folder has "${a}" files;
done
But is there any easy way to filter only the files with less than 440 elements?
This simple script could work for you:-
#!/bin/bash
MAX=440
for folder in sol_cv_*; do
COUNT=$(find "$folder" -type f -name "*.solution" | wc -l)
((COUNT < MAX)) && echo "$folder"
done
The script below
counterfun(){
count=$(find "$1" -maxdepth 1 -type f -iname "*.solution" | wc -l)
(( count < 440 )) && echo "$1"
}
export -f counterfun
find /YOUR/BASE/FOLDER/ -maxdepth 1 -type d -iname "sol_cv_*" -exec bash -c 'counterfun "$1"' _ {} \;
#maxdepth 1 in both find above as you've confirmed no sub-folders
should do it
Avoid parsing ls command and use printf '%q\n for counting files:
for folder in *sol_cv_*/; do
# if there are less than 440 elements then skip
(( $(printf '%q\n' "$folder"/* | wc -l) < 440 )) && continue
# otherwise print the count using safer printf '%q\n'
echo "$folder has $(printf '%q\n' "$folder"*.solution | wc -l) files"
done

issue Find command Linux

I have a folder and I want count all regular files in it, and for this I use this bash command:
find pathfolder -type f 2> err.txt | wc -l
In the folder there are 3 empty text files and a subfolder with inside it other text files.
For this reason I should get 3 as a result, but I get 6 and I don't understand why. Maybe there is some options that I did not set.
If I remove the subfolder I get 4 as result
To grab all the files and directories in current directory without dot files:
shopt -u dotglob
all=(*)
To grab only directories:
dirs=(*/)
To count only non-dot files in current directory:
echo $(( ${#all[#]} - ${#dirs[#]} ))
To do this with find use:
find . -type f -maxdepth 1 ! -name '.*' -exec printf '%.0s.\n' {} + | wc -l
Below solutions ignore the filenames starting with dot.
To count the files in pathfolder only:
find pathfolder -maxdepth 1 -type f -not -path '*/\.*' | wc -l
To count the files in ALL child directories of pathfolder:
find pathfolder -mindepth 2 -maxdepth 2 -type f -not -path '*/\.*' | wc -l
UPDATE: Converting comments into an answer
Based on the suggestions received from anubhava, by creating a dummy file using the command touch $'foo\nbar', the wc -l counts this filename twice, like in below example:
$> touch $'foo\nbar'
$> find . -type f
./foo?bar
$> find . -type f | wc -l
2
To avoid this, get rid of the newlines before calling wc (anubhava's solution):
$> find . -type f -exec printf '%.0sbla\n' {} +
bla
$> find . -type f -exec printf '%.0sbla\n' {} + | wc -l
1
or avoid calling wc at all:
$> find . -type f -exec sh -c 'i=0; for f; do ((i++)); done; echo $i' sh {} +
1

Bash find and expression

Is there some way to make this working?
pFile=find ${destpath} (( -iname "${mFile##*/}" )) -o (( -iname "${mFile##*/}" -a -name "*[],&<>*?|\":'()[]*" )) -exec printf '.' \;| wc -c
i need pFile return the number of file with the same filename, or if there aren't, return 0.
I have to do this, because if i only use:
pFile=find ${destpath} -iname "${mFile##*/}" -exec printf '.' \;| wc -c
It doesn't return if there are same filename with metacharacter.
Thanks
EDIT:
"${mFile##*/}" have as output file name in start folder without path.
echo "${mFile##*/}" -> goofy.mp3
Exmple
in start folder i have:
goofy.mp3 - mickey[1].avi - donald(2).mkv - scrooge.3gp
In destination folder i have:
goofy.mp3 - mickey[1].avi -donald(2).mkv -donald(1).mkv -donald(3).mkv -minnie.iso
i want this:
echo pFile -> 3
With:
pFile=find ${destpath} -iname "${mFile##*/}" -exec printf '.' \;| wc -c
echo pFile -> 2
With:
pFile=find ${destpath} -name "*[],&<>*?|\":'()[]*" -exec printf '.' \;| wc -c
echo pFile -> 4
With Same file name i mean:
/path1/mickey[1].avi = /path2/mickey[1].avi
I am not sure I understood your intended semantics of ${mFile##*/}, however looking at your start/destination folder example, I have created the following use case directory structure and the script below to solve your issue:
$ find root -type f | sort -t'/' -k3
root/dir2/donald(1).mkv
root/dir1/donald(2).mkv
root/dir2/donald(2).mkv
root/dir2/donald(3).mkv
root/dir1/goofy.mp3
root/dir2/goofy.mp3
root/dir1/mickey[1].avi
root/dir2/mickey[1].avi
root/dir2/minnie.iso
root/dir1/scrooge.3gp
Now, the following script (I've used gfind to indicated that you need GNU find for this to work, but if you're on Linux, just use find):
$ pFile=$(($(gfind root -type f -printf "%f\n" | wc -l) - $(gfind root -type f -printf "%f\n" | sort -u | wc -l)))
$ echo $pFile
3
I'm not sure this solves your issue, however it does print the number you expected in your provided example.

Output of cat to bash numeric variable

I have a set of files, each containing a single (integer) number, which is the number of files in the directory of the same name (without the .txt suffix) - the result of a wc on each of the directories.
I would like to sum the numbers in the files. I've tried:
i=0;
find -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read j; do i=$i+`cat $j.txt`; done
echo $i
But the answer is 0. If I simply echo the output of cat:
i=0; find -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read j; do echo `cat $j.txt`; done
The values are there:
1313
1528
13465
22258
7262
6162
...
Presumably I have to cast the output of cat somehow?
[EDIT]
I did find my own solution in the end:
i=0;
for j in `find -mindepth 1 -maxdepth 1 -type d -printf '%f\n'`; do
expr $((i+=$(cat $j.txt)));
done;
28000
30250
...
...
647185
649607
but the accepted answer is neater as it doesn't output along the way
The way you're summing the output of cat should work. However, you're getting 0 because your while loop is running in a subshell and so the variable that stores the sum goes out of scope once the loop ends. For details, see BashFAQ/024.
Here's one way to solve it, using process substitution (instead of pipes):
SUM=0
while read V; do
let SUM="SUM+V"
done < <(find -mindepth 1 -maxdepth 1 -type d -exec cat "{}.txt" \;)
Note that I've taken the liberty of changing the find/cat/sum operations, but your approach should work fine as well.
My one-liner solution without the need of find :
echo $(( $(printf '%s\n' */ | tr -d / | xargs -I% cat "%.txt" | tr '\n' '+')0 ))

Can this be printed on same line?

This command will count the number of files in the sub-directories.
find . -maxdepth 1 -type d |while read dir;do echo "$dir";find "$dir" -type f|wc -l;done
Which looks like
./lib64
327
./bin
118
Would it be possible to have it to look like
327 ./lib64
118 ./bin
instead?
There are a number of ways to do this... Here's something that doesn't change your code very much. (I've put it in multiple lines for readability.)
find . -maxdepth 1 -type d | while read dir; do
echo `find "$dir" -type f | wc -l` "$dir"
done
pipe into tr to remove or replace newlines. I expect you want the newline to be turned into a tab character, like this:
find . -maxdepth 1 -type d |while read dir;do
find "$dir" -type f|wc -l | tr '\n' '\t';
echo "$dir";
done
(Edit: I had them the wrong way around)
do echo -n "$dir "
The -n prevents echo from ending the line afterwards.

Resources