Recursively count specific files BASH - bash

My goal is to write a script to recursively search through the current working directory and the sub dirctories and print out a count of the number of ordinary files, a count of the directories, count of block special files, count of character special files,count of FIFOs, and a count of symbolic links. I have to use condition tests with [[ ]]. Problem is I am not quite sure how to even start.
I tried the something like the following to search for all ordinary files but I'm not sure how recursion exactly works in BASH scripting:
function searchFiles(){
if [[ -f /* ]]; then
return 1
fi
}
searchFiles
echo "Number of ordinary files $?"
but I get 0 as a result. Anyone help on this?

Why would you not use find?
$ # Files
$ find . -type f | wc -l
327
$ # Directories
$ find . -type d | wc -l
64
$ # Block special
$ find . -type b | wc -l
0
$ # Character special
$ find . -type c | wc -l
0
$ # named pipe
$ find . -type p | wc -l
0
$ # symlink
$ find . -type l | wc -l
0

Something to get you started:
#!/bin/bash
directory=0
file=0
total=0
for a in *
do
if test -d $a; then
directory=$(($directory+1))
else
file=$(($file+1))
fi
total=$(($total+1))
echo $a
done
echo Total directories: $directory
echo Total files: $file
echo Total: $total
No recursion here though, for that you could resort to ls -lR or similar; but then again if you are to use an external program you should resort to using find, that's what it's designed to do.

Related

Create a new folder named the next count

I have a folder of projects, which labels projects based on a number. It starts at 001 and continues counting. I have a bash script I run through Alfred, however, I currently have to type the name of the folder.
QUERY={query}
mkdir /Users/admin/Documents/projects/"$QUERY"
I would like to have the script automatically name the folder to the next number.
For example, if the newest folder is "019" then I would like it to automatically name it to "020"
This is what I've whipped up so far:
nextNum = $(find ~/documents/projects/* -maxdepth 1 -type d -print| wc -l)
numP = nextNum + 1
mkdir /Users/admin/Documents/projects/00"$numP"
I'm not sure if my variable syntax is correct, or if variables are the best way to do this. I am a complete noob to bash so any help is appreciated.
This might be what you're looking for:
#!/bin/bash
cd /Users/admin/Documents/projects || exit
if ! [[ -d 000 ]]; then mkdir 000; exit; fi
dirs=([0-9][0-9][0-9])
mkdir $(printf %03d $(( 1 + 10#${dirs[${#dirs[*]} - 1]} )) )
See comments in code to understand the code.
#!/bin/bash
# Configure your project dir
projects_dir=/Users/admin/Documents/projects
# Find the last project number.
# You can use wc instead of the sort|tail|sed I used. The result is the same.
last_project=$(\ls $projects_dir | sort -n | tail -n1 | sed 's/^0*//')
# Use printf to add the leading '0'
# ${last_project:-0} will substitute '0' when $last_project not set,
# therefore it will work even if your project directory is empty.
# $(( expr )) evaluates math expression. Also you can not have
# subshell expression in $(( expr )) so you can't substitute
# $last_project with the expression above
mkdir $projects_dir/$(printf "%03d" $((${last_project:-0} + 1)))
Although unlikely, the issue with the above script is that hitting a project count over 999 will break the 3 digits directory name convention. A simple fix is to use a 4 digits convention. A directory large amount of files/subdirectories is not recommended anyway so if you do reach 9999 projects, it's best to continue in a second project directory.
Give a try to this funny one:
projects_dir="/Users/admin/Documents/projects/"
[[ -n "${HOME}" ]] && projects_dir="${projects_dir/#~/$HOME}"
mkdir "${projects_dir}"/$(printf "%03d" $(find "${projects_dir}" -maxdepth 1 -type d -printf . | wc -c))
(EDIT)
Expanded version with bash trace output:
set -x
projects_dir="/Users/admin/Documents/projects/"
[[ -n "${HOME}" ]] && projects_dir="${projects_dir/#~/$HOME}"
cd "${projects_dir}" || exit 1
printf "each dot is a directory: "
find "${projects_dir}" -maxdepth 1 -type d -printf .
printf "\n"
let next_directory_id=$(find "${projects_dir}" -maxdepth 1 -type d -printf . | wc -c)
next_directory_name=$(printf "%03d" "${next_directory_id}")
mkdir "${projects_dir}"/"${next_directory_name}"
set +x
targ_number=$(find ~/documents/projects/ -maxdepth 1 -type d -print| wc -l)
mkdir /Users/admin/Documents/projects/$(printf "%03d" $targ_number)

counting the total numbers of files and directories in a provided folder including subdirectories and their files

I want to count all the files and directories from a provided folder including files and directories in a subdirectory. I have written a script which will count accurately the number of files and directory but it does not handle the subdirectories any ideas ???
I want to do it without using FIND command
#!/bin/bash
givendir=$1
cd "$givendir" || exit
file=0
directories=0
for d in *;
do
if [ -d "$d" ]; then
directories=$((directories+1))
else
file=$((file+1))
fi
done
echo "Number of directories :" $directories
echo "Number of file Files :" $file
Use find:
echo "Number of directories: $(find "$1" -type d | wc -l)"
echo "Number of files/symlinks/sockets: $(find "$1" ! -type d | wc -l)"
Using plain shell and recursion:
#!/bin/bash
countdir() {
cd "$1"
dirs=1
files=0
for f in *
do
if [[ -d $f ]]
then
read subdirs subfiles <<< "$(countdir "$f")"
(( dirs += subdirs, files += subfiles ))
else
(( files++ ))
fi
done
echo "$dirs $files"
}
shopt -s dotglob nullglob
read dirs files <<< "$(countdir "$1")"
echo "There are $dirs dirs and $files files"
find "$1" -type f | wc -l will give you the files, find "$1" -type d | wc -l the directories
My quick-and-dirty shellscript would read
#!/bin/bash
test -d "$1" || exit
files=0
# Start with 1 to count the starting dir (as find does), else with 0
directories=1
function docount () {
for d in $1/*; do
if [ -d "$d" ]; then
directories=$((directories+1))
docount "$d";
else
files=$((files+1))
fi
done
}
docount "$1"
echo "Number of directories :" $directories
echo "Number of file Files :" $files
but mind it: On my build folder for a project, there were quite some differences:
find: 6430 dirs, 74377 non-dirs
my script: 6032 dirs, 71564 non-dirs
#thatotherguy's script: 6794 dirs, 76862 non-dirs
I assume that has to do with the legions of links, hidden files etc., but I am too lazy to investigate: find is the tool of choice.
Here are some one-line commands that work without find:
Number of directories: ls -Rl ./ | grep ":$" | wc -l
Number of files: ls -Rl ./ | grep "[0-9]:[0-9]" | wc -l
Explanation:
ls -Rl lists all files and directories recursively, one line each.
grep ":$" finds just the results whose last character is ':'. These are all of the directory names.
grep "[0-9]:[0-9]" matches on the HH:MM part of the timestamp. The timestamp only shows up on file, not directories. If your timestamp format is different then you will need to pick a different grep.
wc -l counts the number of lines that matched from the grep.

How to locate the directory where the sum of the number of lines of regular file is greatest (in bash)

Hi i'm new in Unix and bash and I'd like to ask q. how can i do this
The specified directory is given as arguments. Locate the directory
where the sum of the number of lines of regular file is greatest.
Browse all specific directories and their subdirectories. Amounts
count only for files that are directly in the directory.
I try somethnig but it's not working properly.
while [ $# -ne 0 ];
do case "$1" in
-h) show_help ;;
-*) echo "Error: Wrong arguments" 1>&2 exit 1 ;;
*) directories=("$#") break ;;
esac
shift
done
IFS='
'
amount=0
for direct in "${directories[#]}"; do
for subdirect in `find $direct -type d `; do
temp=`find "$subdirect" -type f -exec cat {} \; | wc -l | tr -s " "`
if [ $amount -lt $temp ]; then
amount=$temp
subdirect2=$subdirect
fi
done
echo Output: "'"$subdirect2$amount"'"
done
the problem is here when i use as arguments this dirc.(just example)
/home/usr/first and there are this direct.
/home/usr/first/tmp/first.txt (50 lines)
/home/usr/first/tmp/second.txt (30 lines)
/home/usr/first/tmp1/one.txt (20 lines)
it will give me on Output /home/usr/first/tmp1 100 and this is wrong it should be /home/usr/first/tmp 80
I'd like to scan all directories and all its subdirectories in depth. Also if multiple directories meets the maximum should list all.
Given your sample files, I'm going to assume you only want to look at the immediate subdirectories, not recurse down several levels:
max=-1
# the trailing slash limits the wildcard to directories only
for dir in */; do
count=0
for file in "$dir"/*; do
[[ -f "$file" ]] && (( count += $(wc -l < "$file") ))
done
if (( count > max )); then
max=$count
maxdir="$dir"
fi
done
echo "files in $maxdir have $max lines"
files in tmp/ have 80 lines
In the spirit of Unix (caugh), here's an absolutely disgusting chain of pipes that I personally hate, but it's a lot of fun to construct :):
find . -mindepth 1 -maxdepth 1 -type d -exec sh -c 'find "$1" -maxdepth 1 -type f -print0 | wc -l --files0-from=- | tail -1 | { read a _ && echo "$a $1"; }' _ {} \; | sort -nr | head -1
Of course, don't use this unless you're mentally ill, use glenn jackman's nice answer instead.
You can have great control on find's unlimited filtering possibilities, too. Yay. But use glenn's answer!

Counting the number of files in a directory in bash

I have a bash script where I'm trying to find out the number of files in a directory and perform an addition operation on it as well.
But while doing the same I'm getting the error as follows:
admin> ./fileCount.sh
1
./fileCount.sh: line 6: 22 + : syntax error: operand expected (error token is " ")
My script is as shown:
#!/usr/bin/bash
Var1=22
Var2= ls /stud_data/Input_Data/test3 | grep ".txt" | wc -l
Var3= $(($Var1 + $Var2))
echo $Var3
Can anyone point out where is the error.
A little away
As #devnull already answered to the question point out where is the error,
Just some more ideas:
General unix
To make this kind of browsing, there is a very powerfull command find that let you find recursively, exactly what you're serching for:
Var2=`find /stud_data/Input_Data/test3 -name '*.txt' | wc -l`
If you won't this to be recursive:
Var2=`find /stud_data/Input_Data/test3 -maxdepth 1 -name '*.txt' | wc -l`
If you want files only (meaning no symlink, nor directories)
Var2=`find /stud_data/Input_Data/test3 -maxdepth 1 -type f -name '*.txt' | wc -l`
And so on... Please read the man page: man find.
Particular bash solutions
As your question stand for bash, there is some bashism you could use to make this a lot quicker:
#!/bin/bash
Var1=22
VarLs=(/stud_data/Input_Data/test3/*.txt)
[ -e $VarLs ] && Var2=${#VarLs[#]} || Var2=0
Var3=$(( Var1 + Var2 ))
echo $Var3
# Uncomment next line to see more about current environment
# set | grep ^Var
Where bash expansion will translate /path/*.txt in an array containing all filenames matching the jocker form.
If there is no file matching the form, VarLs will only contain the jocker form himself.
So the test -e will correct this: If the first file of the returned list exist, then assing the number of elements in the list (${#VarLs[#]}) to Var2 else, assign 0 to Var2.
Can anyone point out where is the error.
You shouldn't have spaces around =.
You probably wanted to use command substitution to capture the result in Var2.
Try:
Var1=22
Var2=$(ls /stud_data/Input_Data/test3 | grep ".txt" | wc -l)
Var3=$(($Var1 + $Var2))
echo $Var3
Moreover, you could also say
Var3=$((Var1 + Var2))

Bash script to list files not found

I have been looking for a way to list file that do not exist from a list of files that are required to exist. The files can exist in more than one location. What I have now:
#!/bin/bash
fileslist="$1"
while read fn
do
if [ ! -f `find . -type f -name $fn ` ];
then
echo $fn
fi
done < $fileslist
If a file does not exist the find command will not print anything and the test does not work. Removing the not and creating an if then else condition does not resolve the problem.
How can i print the filenames that are not found from a list of file names?
New script:
#!/bin/bash
fileslist="$1"
foundfiles="~/tmp/tmp`date +%Y%m%d%H%M%S`.txt"
touch $foundfiles
while read fn
do
`find . -type f -name $fn | sed 's:./.*/::' >> $foundfiles`
done < $fileslist
cat $fileslist $foundfiles | sort | uniq -u
rm $foundfiles
#!/bin/bash
fileslist="$1"
while read fn
do
FPATH=`find . -type f -name $fn`
if [ "$FPATH." = "." ]
then
echo $fn
fi
done < $fileslist
You were close!
Here is test.bash:
#!/bin/bash
fn=test.bash
exists=`find . -type f -name $fn`
if [ -n "$exists" ]
then
echo Found it
fi
It sets $exists = to the result of the find. the if -n checks if the result is not null.
Try replacing body with [[ -z "$(find . -type f -name $fn)" ]] && echo $fn. (note that this code is bound to have problems with filenames containing spaces).
More efficient bashism:
diff <(sort $fileslist|uniq) <(find . -type f -printf %f\\n|sort|uniq)
I think you can handle diff output.
Give this a try:
find -type f -print0 | grep -Fzxvf - requiredfiles.txt
The -print0 and -z protect against filenames which contain newlines. If your utilities don't have these options and your filenames don't contain newlines, you should be OK.
The repeated find to filter one file at a time is very expensive. If your file list is directly compatible with the output from find, run a single find and remove any matches from your list:
find . -type f |
fgrep -vxf - "$1"
If not, maybe you can massage the output from find in the pipeline before the fgrep so that it matches the format in your file; or, conversely, massage the data in your file into find-compatible.
I use this script and it works for me
#!/bin/bash
fileslist="$1"
found="Found:"
notfound="Not found:"
len=`cat $1 | wc -l`
n=0;
while read fn
do
# don't worry about this, i use it to display the file list progress
n=$((n + 1))
echo -en "\rLooking $(echo "scale=0; $n * 100 / $len" | bc)% "
if [ $(find / -name $fn | wc -l) -gt 0 ]
then
found=$(printf "$found\n\t$fn")
else
notfound=$(printf "$notfound\n\t$fn")
fi
done < $fileslist
printf "\n$found\n$notfound\n"
The line counts the number of lines and if its greater than 0 the find was a success. This searches everything on the hdd. You could replace / with . for just the current directory.
$(find / -name $fn | wc -l) -gt 0
Then i simply run it with the files in the files list being separated by newline
./search.sh files.list

Resources