bash returning from recursion - bash

I have to search all subdirs recursively and print *(number of * = depth of file/dir) type and name. The problem comes when i enter dir and then want to get out but nothing happens.
my test file
DIR test
*FILE ace
*FILE base
*DIR father
**FILE cookies
*DIR mother
**DIR how
***FILE youdoing
*FILE zebra
my code
maxDepth is how far in to the dir it can go(default 3) and currDepth is 1 at the beginning
function tree(){
maxDepth=$2
currDepth=$3
#print the starting file
if [ "$currDepth" -eq 0 ];then
printf "%s %s\n" DIR "$1"
currDepth=1
fi
for path in "$1"/*;do
for i in $( seq 1 $currDepth );do
echo -n *
done
if [ -d "$path" ];then
printf "%s %s\n" DIR "${path##*/}"
if [[ "$currDepth" -lt "$maxDepth" ]];then
tree "$path" "$maxDepth" "$(( currDepth + 1 ))"
fi
continue
fi
if [ -f "$path" ];then
printf "%s %s\n" FILE "${path##*/}"
continue
fi
if [ -L "$path" ];then
printf "%s %s\n" LINK "${path##*/}"
continue
fi
done
}
my output
DIR test
*FILE ace
*FILE base
*DIR father
**FILE cookies
**DIR mother
***DIR how
***FILE zebra
what am i doing wrong

Debug your script by doing set -x before you run it.
Make sure integers are always integers by declaring them with the -i integer attribute.
Use expression syntax consistently. It's a good idea to always use [[ ]] tests for string comparisons and (( )) for arithmetic and numeric comparisons, if your target shell is bash.
Use (( )) for loops instead of seq, which is nonstandard.
Explicitly declare your variables in the function (using local or declare) to ensure they are scoped to the function.
Actually call the inner tree.
#!/bin/bash
tree() {
local -i maxDepth=$2 # Make sure these values are always integers
local -i currDepth=$3
# print the starting file
if (( currDepth == 0 )); then # use bash arithmetic
printf "%s %s\n" DIR "$1"
currDepth=1
fi
for path in "$1"/*;do
for ((i=0; i<currDepth; i++)); do
printf '*'
done
if [[ -d "$path" ]];then
printf "%s %s\n" DIR "${path##*/}"
if [[ "$currDepth" -lt "$maxDepth" ]];then
tree "$path" "$maxDepth" "$(( currDepth + 1 ))"
fi
continue
fi
if [[ -f "$path" ]];then
printf "%s %s\n" FILE "${path##*/}"
continue
fi
if [[ -L "$path" ]];then
printf "%s %s\n" LINK "${path##*/}"
continue
fi
done
}

Here a solution using find, stat and sed:
find <DIR> -exec stat --printf="%n,%F\n" "{}" \; | \
sed -r -e "s/[^\/]+\//\*/g" -e "s/regular file/FILE/" -e "s/directory/DIR/" | \
sed -r -e "s/([\*]+)([^,]+),(.+)/\1 \3 \2/"
IMPORTANT: Use DIR not DIR/ otherwise DIR name will not appear in results.
Explanation:
find returns recursively all files and directory within DIR.
-exec option in find allows to pass each result to another command.
Here I'm passing each result to the command stat
stat has an option to format the output -printf (see manpage) :
%n is the filename (with relavtive path)
%F is the file type ( regular file, directory,symbolic link,block special file...)
So,
find <DIR> -exec stat --printf="%n,%F\n" "{}" \;
returns the following, one result by line (assuming that there are only regular files and directories in DIR) :
DIR/path/to/file,regular file
DIR/path/to/dir,directory
Then, I'm using sed to transform each line the way you required using regular expression:
Replace string/ by * -> ***basename,file type
Replace "regular file" by FILE
Replace "directory" by DIR
Shuffle around basename and filetype using back referencing in sed.
NOTE: I will not explain in details how regular expressions work as it would be too long.

I should have used local in front of currDepth=$3

Related

else part of an if/else block never runs in Bash script

In the below function, I'm trying to print the file name if there are any files in the directory or an error if there's not, but the else part of the inner if/else block, which prints the error message, never runs.
It should be something logical but I can't figure it out.
walk_dir () {
shopt -s nullglob dotglob
for pathname in "$1"/*; do
if [ -d "$pathname" ]; then
printf "\nFiles under $(basename "$pathname")\n";
printf "==========================================\n";
walk_dir "$pathname";
else
fc=$(find "$pathname" -maxdepth 1 -type f | wc -l);
# Here is the problem
if [ $fc -gt 0 ] && [ ! -z $fc ]; then
printf '%s\n' $(basename "$pathname");
else
printf '\e[30mNo candidate file found.\e[39m\n';
fi
fi
done
}
There are 3 cases for walk_dir
Non-empty folder
Empty folder
Regular file
Ignoring special file, probably N/A here.
When called for non-empty folder, walk_dir will make (recursive) on any sub folder (line #7), and then will print the base name of every regular file in line #12.
When called with empty folder, walk_dir will do nothing since "$1"/* expand to empty list.
When the directory contain files, the code will "count" the number of files in line #9. This l only process non-directories - probably files (assuming not special devices, etc). In this case it will make recursive call for each entry in the folder.
If there are regular files in the folder, the code will execute the find on line #8. On a regular file will always set fc="1", therefore the condition on line 11 will always be true, never getting into the else part on line #13.
1 walk_dir () {
2 shopt -s nullglob dotglob
3 for pathname in "$1"/*; do
4 if [ -d "$pathname" ]; then
5 printf "\nFiles under $(basename "$pathname")\n";
6 printf "==========================================\n";
7 walk_dir "$pathname";
8 else
9 fc=$(find "$pathname" -maxdepth 1 -type f | wc -l);
10 # Here is the problem
11 if [ $fc -gt 0 ] && [ ! -z $fc ]; then
12 printf '%s\n' $(basename "$pathname");
13 else
14 printf '\e[30mNo candidate file found.\e[39m\n';
15 fi
16 fi
17 done
18 }
According to #dash-o explanations I put the if condition before iterating over files and, it worked as I expected.
walk_dir () {
shopt -s nullglob dotglob
fc=$(find "$1" -maxdepth 1 -type f | wc -l);
if [ $fc -eq 0 ] || [ -z $fc ]; then
printf '\e[30mNo candidate file found.\e[0m\n';
fi
for pathname in "$1"/*; do
if [ -d "$pathname" ]; then
printf "\nFiles under $(basename "$pathname")\n";
printf "==========================================\n";
walk_dir "$pathname";
else
printf '%s\n' $(basename "$pathname");
fi
done
}
Too long for a comment!
I don't understand the usage of
fc=$(find "$pathname" -maxdepth 1 -type f | wc -l);
In that part of the code $pathname is either a file (not directory) or the search pattern of the for loop, if no file was found. So this code seems better:
if [[ -f $filename ]]
then
# use input redirection to suppress filename
fc=$(wc -l <"$filename")
else
echo "not a plain file"
fi

(unix) How to write script to count the number of files in a directory using loop

How can I create a bash script to count the number of files in a directory using a loop.
The script should take a target directory and output: Number of files in ::
#!/bin/bash
counter=0
if [ ! -d "$1" ]
then
printf "%s\n" " $1 is not a directory"
exit 0
fi
directory="$1"
number="${directory##*/}"
number=${#number}
if [ $number -gt 0 ]
then
directory="$directory/"
fi
for line in ${directory}*
do
if [ -d "$line" ]
then
continue
else
counter=$(( $counter + 1))
fi
done
printf "%s\n" "Number of files in $directory :: $counter"
I would use (GNU) find and wc:
find /path/to/dir -maxdepth 1 -type f -printf '.' | wc -c
The above find command prints a dot for every file in the directory and wc -c counts those dots. This would work well with any kind of special character (including whitespaces and newlines) in the filenames.
You don't really need a loop. The following will count the files in a directory:
files=($(ls $1))
echo ${#files[#]}

Bash Find Function Ubuntu - Find in directory tree, files that have the same name as their directory

I want to find and print files in directory tree, that have the sname name as theirs dirs.
This is my code so far:
#!bin/bash
if [ $# -eq 0 ]
then
echo "No args"
fi
if [[ -d $1 ]] #if its dir
then
find $1 -type f | (while read var1 #for every regular file in dir tree
do
if [[ -f $var1 ]]
then
echo $var1 #full path
# I dont know how to get the dir name
echo $(basename $var1) #file name
echo
#then compare it and print full path
fi
done)
fi
I want to do this using FIND function in bash linux. Thanks
You can use this script with find:
while IFS= read -rd '' f; do
d="${f%/*}"
[[ ${d##*/} == ${f##*/} ]] && echo "$f"
done < <(find . -type f -print0)

Error on my code

#!/bin/bash
# When a match is not found, just present nothing.
shopt -s nullglob
files=(*.wav)
if [[ ${#files[#]} -eq 0 ]]; then
echo "No match found."
fi
for file in "${files[#]}"; do
# We get the date part
find_date=$(stat -c %y $file | awk '{print $1}')`
for t in "${parts[#]}"; do
IFS="-." read -ra parts <<< "$file"
if [[ $t == [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then
file_date=$t
break
fi
done
# If a value was not assigned, then show an error message and continue to the next file.
# Just making sure there is nothing in Array and date before it moves on
if [[ -z $file_date ]]; then
continue
fi
file_year=${file_date:0:4}
file_month=${file_date:6:2}
mkdir -p "$file_year/$file_month"
# -- is just there to not interpret filenames starting with - as options.
echo "Moving: ./"$file "to: " "./"$file_year"/"$file_month
mv "$file" "$file_year/$file_month"
done
I have some files that are .wav.... I want to put the files in an array like I did and then Stat -c %y filename |awk $1 which gives me YYYY-MM-DD and then I wanna put the date in the array so then I can set it 2 variables Year and Month so then I can either make a DIR Year/Month or if DIR is already there then just mv it. which is mkdir -p... Geting errors in my code but I do not think i am reading the file in my array correct.
25: continue: only meaningful in a for',while', or `until' loop
my echo statement Moving: ./OUT117-20092025-5845.wav to: .//
Besides some syntax issues main problem is that you cannot have continue outside for loop.
Syntax error are:
You cannot have space on either side of assignment operator = so find_date= stat -c %y $file | awk{print $1}` should befind_date=$(stat -c %y $file | awk '{print $1}')`
Regex operator is =~ instead of ==
UPDATE: You are starting your for loop before you are setting your variable.
for t in "${parts[#]}"; do
IFS="-." read -ra parts <<< "$file"
It should be:
IFS="-." read -ra parts <<< "$file"
for t in "${parts[#]}"; do

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