Recursively count directories and files with a shell script - bash

I'm trying to write a shell script that will recursively count all the files and sub-directories in a directory and also all the hidden ones. My script can count them however it can't detect hidden files and directories that are in a sub-directory. How can i change it so that it is able to do this? Also i cannot use find, du or ls -R
#!/bin/bash
cd $1
dir=0
hiddendir=0
hiddenfiles=0
x=0
items=( $(ls -A) )
amount=( $(ls -1A | wc -l) )
counter() {
if [ -d "$i" ]; then
let dir+=1
if [[ "$i" == .* ]]; then
let hiddendir+=1
let dir-=1
fi
search "$i"
elif [ -f "$i" ]; then
let files+=1
if [[ "$i" == .* ]]; then
let files-=1
let hiddenfiles+=1
fi
fi
}
search() {
for i in $1/*; do
counter "$i"
done
}
while [ $x -lt $amount ]; do
i=${items[$x]}
counter "$i"
let x+=1
done

#!/bin/bash -e
shopt -s globstar dotglob # now ** lists all entries recursively
cd "$1"
dir=0 files=0 hiddendir=0 hiddenfiles=0
counter() {
if [ -f "$1" ]; then local typ=files
elif [ -d "$1" ]; then local typ=dir
else continue
fi
[[ "$(basename "$1")" == .* ]] && local hid=hidden || local hid=""
((++$hid$typ))
}
for i in **; do
counter "$i"
done
echo $dir $files $hiddendir $hiddenfiles

Consider using this:
find . | wc -l

Related

Recursively search for files

I am trying to find all files by passing a directory name in all sub directories meaning the process is recursive here is my code
myrecursive() {
if [ -f $1 ]; then
echo $1
elif [ -d $1 ]; then
for i in $(ls $1); do
if [ -f $1 ]; then
echo $i
else
myrecursive $i
fi
done
else
echo " sorry"
fi
}
myrecursive $1
However when I pass directory with another directory I get 2 times sorry,where is my mistake?
The goal that you are trying to achieve could be simply done by using find command:
# will search for all files recursively in current directory
find . * -exec echo {} \;
# will search for all *.txt file recursively in current directory
find . -name "*.txt" -exec echo {} \;
# will search for all *.txt file recursively in current directory
# but depth is limited to 3
find . -name "*.txt" -max-depth 3 -exec echo {} \;
See man find for manual. How to run find -exec?
The problem with your code is quite simple.
The ls command will return a list of filenames, but they aren't valid for
recursion. Use globbing instead. The loop below simply replaces $(ls) with $1/*
myrecursive() {
if [ -f $1 ]; then
echo $1
elif [ -d $1 ]; then
for i in $1/*; do
if [ -f $1 ]; then
echo $i
else
myrecursive $i
fi
done
else
echo " sorry"
fi
}
myrecursive $1
Hope that helps
#!/bin/bash
myrecursive() {
if [ -f "$1" ]; then
echo "$1"
elif [ -d "$1" ]; then
for i in "$1"/*; do
if [ -f "$i" ]; then #here now our file is $i
echo "$i"
else
myrecursive "$i"
fi
done
else
echo " sorry"
fi
}
myrecursive "$1"

bash check all file in a directory for their extension

I am writing a shell script to read all the files in the give directory by the user input then count how many files with that extension. I just started learning Bash and I am not sure why it this not locating the files or reading the directory. I am only putting 2 example but my count is always 0.
This is how I run my script
$./check_ext.sh /home/user/temp
my script check_ext.sh
#!/bin/bash
count1=0
count2=0
for file in "ls $1"
do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done;
echo $count $count2
"ls $1" doesn't execute ls on $1, it just a plain string. Command substitution syntax is $(ls "$1")
However there is no need to use ls, just use globbing:
count1=0
count2=0
for file in "$1"/*; do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done
echo "counts: $count1 $count2"
for file in "$1"/* will iterate through all the files/directories in the directory denoted by $1
EDIT: For doing it recursively inside a directory:
count1=0
count2=0
while IFS= read -r -d '' file; do
if [[ $file == *.sh ]]; then
echo "is a txt file"
(( count1++ ))
elif [[ $file == *.mp3 ]]; then
echo "is a mp3 file"
(( count2++ ))
fi
done < <(find "$1" -type f -print0)
echo "counts: $count1 $count2"
POSIXly:
count1=0
count2=0
for f in "$1"/*; do
case $f in
(*.sh) printf '%s is a txt file\n' "$f"; : "$((count1+=1))" ;;
(*.mp3) printf '%s is a mp3 file\n' "$f"; : "$((count2+=1))" ;;
esac
done
printf 'counts: %d %d\n' "$count1" "$count2"
You can use Bash arrays for this too: if you only want to deal with extensions sh and mp3:
#!/bin/bash
shopt -s nullglob
shs=( "$1"/*.sh )
mp3s=( "$1"/*.mp3 )
printf 'counts: %d %d\n' "${#shs[#]}" "${#mp3s[#]}"
If you want to deal with more extensions, you can generalize this process:
#!/bin/bash
shopt -s nullglob
exts=( .sh .mp3 .gz .txt )
counts=()
for ext in "${exts[#]}"; do
files=( "$1"/*."$ext" )
counts+=( "${#files[#]}" )
done
printf 'counts:'
printf ' %d' "${counts[#]}"
echo
If you want to deal with all extensions (using associative arrays, available in Bash ≥4)
#!/bin/bash
shopt -s nullglob
declare -A exts
for file in "$1"/*.*; do
ext=${file##*.}
((++'exts[$ext]'))
done
for ext in "${!exts[#]}"; do
printf '%s: %d\n' "$ext" "${exts[$ext]}"
done

Recursively iterating through subdirectories and removing certain file

I have a music archive with lots of folders and sub-folders (Cover Art etc.) so instead of manually removing hundreds of Folder.jpg, Desktop.ini and Thumb.db files, I decided to do a simple bash script but things got really messy.
I did a simple test by creating dummy folders like this:
/home/dummy/sub1 -
sub1sub1
sub1sub1sub1
sub1sub1sub2
sub2 -
sub2sub1
sub2sub2
sub2sub2sub1
and copied some random .jpg, .mp3, .ini files across these folders. My bash script looks currently like this:
function delete_jpg_ini_db {
if [[ $f == *.jpg ]]; then
echo ".jpg file, removing $f"
gvfs-trash $f
elif [[ $f == *.ini ]]; then
echo ".ini file, removing $f"
gvfs-trash -f $f
elif [[ $f == *.db ]]; then
echo ".db file, removing $f"
gvfs-trash -f $f
else echo "not any .jpg, .ini or .db file, skipping $f"
fi
}
function iterate_dir {
for d in *; do
if [ -d $d ]; then
echo "entering sub-directory: $d" && cd $d
pwd
for f in *; do
if [ -f $f ]; then #check if .jpg, .ini or .db, if so delete
delete_jpg_ini_db
elif [ -d $f ]; then #enter sub-dir and iterate again
if [ "$(ls -A $f)" ]; then
iterate_dir
else
echo "sub-directory $f is empty!"
fi
fi
done
fi
done
}
pwd
iterate_dir
When I run it, it successfully iterates through sub1, sub1sub1 and sub1sub1sub1, but it halts there instead of going back to home and searching sub2 next.
I am new in Bash scripting, all help is appreciated..
Thanks.
And in one command you can run:
find /home/dummy/sub1 -name "*.jpg" -o -name "*.ini" -o -name "*.db" -delete
And if you want to see which files would be deleted, replace -delete with -print (just filenames) or with -ls (like ls -l output).
here is the changed code....
function delete_jpg_ini_db {
if [[ $f == *.jpg ]]; then
echo ".jpg file, removing $f"
gvfs-trash $f
elif [[ $f == *.ini ]]; then
echo ".ini file, removing $f"
gvfs-trash -f $f
elif [[ $f == *.db ]]; then
echo ".db file, removing $f"
gvfs-trash -f $f
else echo "not any .jpg, .ini or .db file, skipping $f"
fi
}
function iterate_dir {
for d in *; do
if [ -d "$d" ]; then
echo "entering sub-directory: $d" && cd $d
pwd
for f in *; do
if [ -f "$f" ]; then #check if .jpg, .ini or .db, if so delete
delete_jpg_ini_db
elif [ -d $f ]; then #enter sub-dir and iterate again
if [ "$(ls -A $f)" ]; then
iterate_dir
else
echo "sub-directory $f is empty!"
fi
fi
done
cd ..
fi
done
}
pwd
iterate_dir
Mistakes
You did have support for file name with space in them
You did not navigate back after your inner for loop..
Try it...

Bash script loop through subdirectories and write to file without using find,ls etc

Sorry for asking this question again. I have already received answer but with using find but unfortunately I need to write it without using any predefined commands.
I am trying to write a script that will loop recursively through the subdirectories in the current directory. It should check the file count in each directory. If file count is greater than 10 it should write all names of these file in file named "BigList" otherwise it should write in file "ShortList". This should look like:
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
My script only works if subdirectories don't include subdirectories in turn.
I am confused about this because it doesn't work as I expect.
Here is my script
#!/bin/bash
parent_dir=""
if [ -d "$1" ]; then
path=$1;
else
path=$(pwd)
fi
parent_dir=$path
loop_folder_recurse() {
local files_list=""
local cnt=0
for i in "$1"/*;do
if [ -d "$i" ];then
echo "dir: $i"
parent_dir=$i
echo before recursion
loop_folder_recurse "$i"
echo after recursion
if [ $cnt -ge 10 ]; then
echo -e "---"$parent_dir >> BigList
echo -e $file_list >> BigList
else
echo -e "---"$parent_dir >> ShortList
echo -e $file_list >> ShortList
fi
elif [ -f "$i" ]; then
echo file $i
if [ $cur_fol != $main_pwd ]; then
file_list+=$i'\n'
cnt=$((cnt + 1))
fi
fi
done
}
echo "Base path: $path"
loop_folder_recurse $path
How can I modify my script to produce the desired output?
This bash script produces the output that you want:
#!/bin/bash
bigfile="$PWD/BigList"
shortfile="$PWD/ShortList"
shopt -s nullglob
loop_folder_recurse() {
(
[[ -n "$1" ]] && cd "$1"
for i in */; do
[[ -d "$i" ]] && loop_folder_recurse "$i"
count=0
files=''
for j in *; do
if [[ -f "$j" ]]; then
files+="$j"$'\n'
((++count))
fi
done
if ((count > 10)); then
outfile="$bigfile"
else
outfile="$shortfile"
fi
echo "$i" >> "$outfile"
echo "$files" >> "$outfile"
done
)
}
loop_folder_recurse
Explanation
shopt -s nullglob is used so that when a directory is empty, the loop will not run. The body of the function is within ( ) so that it runs within a subshell. This is for convenience, as it means that the function returns to the previous directory when the subshell exits.
Hopefully the rest of the script is fairly self-explanatory but if not, please let me know and I will be happy to provide additional explanation.

loop over directories to echo its content

The directories are variables set to the full-path
for e in "$DIR_0" "$DIR_1" "$DIR_2"
do
for i in $e/*
do
echo $i
done
The output for each line is the full path. I want only the name of each file
You are looking for basename.
This is the Bash equivalent of basename:
echo "${i##*/}"
It strips off everything before and including the last slash.
If you truly do not wish to recurse you can achieve that more succinctly with this find command:
find "$DIR_0" "$DIR_1" "$DIR_2" -type f -maxdepth 1 -exec basename{} \;
If you wish to recurse over subdirs simply leave out maxdepth:
find "$DIR_0" "$DIR_1" "$DIR_2" -type f -exec basename{} \;
to traveling a directory recursively with bash
try this you can find it here
#! /bin/bash
indent_print()
{
for((i=0; i < $1; i++)); do
echo -ne "\t"
done
echo "$2"
}
walk_tree()
{
local oldifs bn lev pr pmat
if [[ $# -lt 3 ]]; then
if [[ $# -lt 2 ]]; then
pmat=".*"
else
pmat="$2"
fi
walk_tree "$1" "$pmat" 0
return
fi
lev=$3
[ -d "$1" ] || return
oldifs=$IFS
IFS=""
for el in $1/ *; do
bn=$(basename "$el")
if [[ -d "$el" ]]; then
indent_print $lev "$bn/"
pr=$( walk_tree "$el" "$2" $(( lev + 1)) )
echo "$pr"
else
if [[ "$bn" =~ $2 ]]; then
indent_print $lev "$bn"
fi
fi
done
IFS=$oldifs
}
walk_tree "$1" "\.sh$"
See also the POSIX compliant Bash functions to replace basename & dirname here:
http://cfaj.freeshell.org/src/scripts/

Resources