How to exit/break from nested if-else statement once conditions are met - shell

I have a script that uses nested if-else statements to search for files. I want the script to exit once the conditions are met for any one of the nested statement.
But the script still continues to run through the all the remaining if-else statements.
I have tested using exit 0 and return 0 but neither works.
Here's the script:
#!/bin/sh
PATH1=/filer1_vol1_dir1
PATH2=/filer2_vol1_dir1
PATH3=/filer3_vol1_dir2
PATTERN=fruits
find $PATH1 -type f -name "*$PATTERN*" -exec ls -l {} \; >> /tmp/${PATTERN}_search
if [[ -s /tmp/${PATTERN}_search && `grep -i apples /tmp/${PATTERN}_search` ]]
then
echo "Matching files have been found under $PATH1"
cat /tmp/${PATTERN}_search
return 0
else
echo "No matching files, proceeding to search $PATH2"
find $PATH2 -type f -name "*$PATTERN*" -exec ls -l {} \; >> /tmp/${PATTERN}_search
if [[ -s /tmp/${PATTERN}_search && `grep -i apples /tmp/${PATTERN}_search` ]]
then
echo "Matching files have been found under $PATH2"
cat /tmp/${PATTERN}_search
return 0
else
echo "No matching files, proceeding to search $PATH3"
find $PATH3 -type f -name "*$PATTERN*" -exec ls -l {} \; >> /tmp/${PATTERN}_search
if [[ -s /tmp/${PATTERN}_search && `grep -i apples /tmp/${PATTERN}_search` ]]
then
echo "Matching files have been found under $PATH3"
cat /tmp/${PATTERN}_search
return 0
else
echo "No file matches, please search elsewhere"
return 0
fi
fi
fi
exit 0

I have found that the better way is to use a while loop to iterate through each search. Within each iteration, an if-else condition will test if files matching the find pattern are found. Once this condition is true, a break statement was able to stop the script.
Sample script below:
#!/bin/sh
PATH1=/filer1_vol1_dir1
PATH2=/filer2_vol1_dir1
PATH3=/filer3_vol1_dir2
PATTERN=fruits
echo $PATH1 > /tmp/PATH.list
echo $PATH2 >> /tmp/PATH.list
echo $PATH3 >> /tmp/PATH.list
echo /tmp/PATH.list contains
cat /tmp/PATH.list
echo
cat /dev/null > /tmp/${PATTERN}_search.list
while read PATH
do
echo "Searching under the following parameters"
echo PATTERN = $PATTERN
echo PATH = $PATH
echo
/usr/bin/find $PATH -type f -name "*$PATTERN*" -exec ls -l {} \; >> /tmp/${PATTERN}_search.list
/usr/bin/grep -i apples /tmp/${PATTERN}_search.list
if [ $? -eq 0 ]
then
echo "All matching files have been found."
break
else
echo "No matches found, continuing search in next directory."
fi
done < /tmp/PATH.list
exit 0

Related

How to list files and match first line in bash script?

I would like to check for (only) python files for those which do not have the #!/usr/bin/env python in the first line. So, I write a bash script that does the following:
#!/bin/bash
#list all of python files
for file in `find . -name "*.py"`
do
if [ `head -1 $file` != "#!/usr/bin/env python"] then;
echo "no match in file $file"
else then;
echo "match!"
fi
done
However, for some reason I cannot get the if statement correct! I've looked at many questions, but I cannot find one that succinctly describes the issue. Here is the error message:
./run_test.sh: line 9: syntax error near unexpected token `else'
./run_test.sh: line 9: ` else then;'
where am I going awry? Thank you.
You can do something like
find . -type f -name '*.py' -exec \
awk 'NR==1 && /#!\/usr\/bin\/env python/ \
{ print "Match in file " FILENAME; exit } \
{ print "No match in file " FILENAME; exit }' \
{} \;
If you are going to loop over it, don't use a for loop
#!/bin/bash
find . -type f -name '*.py' -print0 | while IFS= read -r -d $'\0' file; do
if [[ $(head -n1 "$file") == "#!/usr/bin/env python" ]]; then
echo "Match in file [$file]"
else
echo "No match in file [$file]"
fi
done
Things to notice:
The [] after your if statement needs correct spacing
The ';' (if you enter a new line is not necessary) goes after the if and not after the then
You added an extra then after the else.
#!/bin/bash
#list all of python files
for file in `find . -name "*.py"`
do
if [ `head -1 $file` != "#!/usr/bin/env python" ];
then
echo "no match in file $file"
else
echo "match!"
fi
done
can you use -exec option by any chance? I find it easier.
find . -name "*.py" -exec head -1 {} | grep -H '#!/usr/bin/env python' \;
You can control the output using grep options.
edit
Thanks to #chepner - To avoid the pipe being swallowed too early:
-exec sh -c "head -1 {} | grep -H '#!/usr/bin/env python'" \;

How to store the grep output into a variable inside a loop?

I would like to count the number of outputs produced by this loop
cd /System/Library/Extensions
find *.kext -prune -type d | while read d; do
codesign -v "$d" 2>&1 | grep "invalid signature"
done
How can I store or count the outputs? If tried with arrays, counters etc. but it seems I can't get anything outside of that loop.
To obtain the number of lines produced by the while loop, the wc word count can be used
cd /System/Library/Extensions
find *.kext -prune -type d | while read d; do
codesign -v "$d" 2>&1 | grep "invalid signature"
done | wc -l
wc -l the -l option counts the number of lines in the input, which is piped to the output of while
Now if you need to count the number of grep outputs in each iteration of the while loop, the -c option of grep would be usefull.
cd /System/Library/Extensions
find *.kext -prune -type d | while read d; do
codesign -v "$d" 2>&1 | grep -c "invalid signature"
done
-c Suppress normal output; instead print a count of matching lines
for each input file
classical difficulty in subshell is to pass variables back.
here one way to get this information back :
cd /System/Library/Extensions
RESULT=$(find *.kext -prune -type d | {
# we are in a sub-subshell ...
GCOUNT=0
DIRCOUNT=0
FAILDIR=0
while read d; do
COUNT=$(codesign -v "$d" 2>&1 | grep -c "invalid signature")
if [[ -n $COUNT ]]
then
if [[ $COUNT > 0 ]]
then
echo "[ERROR] $COUNT invalid signature found in $d" >&2
GCOUNT=$(( $GCOUNT + $COUNT ))
FAILDIR=$(( $FAILDIR + 1 ))
fi
else
echo "[ERROR] wrong invalid signature count for $d" >&2
fi
DIRCOUNT=$(( $DIRCOUNT + 1 ))
done
# this is the actual result, that's why all other output of this subshell are redirected
echo "$DIRCOUNT $FAILDIR $GCOUNT"
})
# parse result integers separated by a space.
if [[ $RESULT =~ ([0-9]+)\ ([0-9]+)\ ([0-9]+) ]]
then
DIRCOUNT=${BASH_REMATCH[1]}
FAILDIR=${BASH_REMATCH[2]}
COUNT=${BASH_REMATCH[3]}
else
echo "[ERROR] Invalid result format. Please check your script $0" >&2
fi
if [[ -n $COUNT ]]
then
echo "$COUNT errors found in $FAILDIR/$DIRCOUNT directories"
fi

Bash script loop through subdirectories and write to file

I have no idea I have spent a lot of hours dealing with this problem. I need to write script. Script should loop recursively through subdirectories in current directory. It should check files 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 subdirecotries don't include subdirectories in turn.
I am confused about this. Because it doesn't work as I expect. It will take less than 5 minutes to write this on any programming language for my.
Please help to solve this problem , because I have no idea how to do this.
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
I believe that this does what you want:
find . -type d -exec env d={} bash -c 'out=Shortlist; [ $(ls "$d" | wc -l) -ge 10 ] && out=Biglist; { echo "--$d"; ls "$d"; echo; } >>"$out"' ';'
If we don't want either to count subdirectories to the cut-off or to list them in the output, then use this version:
find . -type d -exec env d={} bash -c 'out=Shortlist; [ $(ls -p "$d" | grep -v "/$" | wc -l) -ge 10 ] && out=Biglist; { echo "--$d"; ls -p "$d"; echo; } | grep -v "/$" >>"$out"' ';'

Is there a way to pipe from a variable?

I'm trying to find all files in a file structure above a certain file size, list them, then delete them. What I currently have looks like this:
filesToDelete=$(find $find $1 -type f -size +$2k -ls)
if [ -n "$filesToDelete" ];then
echo "Deleting files..."
echo $filesToDelete
$filesToDelete | xargs rm
else
echo "no files to delete"
fi
Everything works, except the $filesToDelete | xargs rm, obviously. Is there a way to use pipe on a variable? Or is there another way I could do this? My google-fu didn't really find anything, so any help would be appreciated.
Edit: Thanks for the information everyone. I will post the working code here now for anyone else stumbling upon this question later:
if [ $(find $1 -type f -size +$2k | wc -l) -ge 1 ]; then
find $1 -type f -size +$2k -exec sh -c 'f={}; echo "deleting file $f"; rm $f' {} \;
else
echo "no files above" $2 "kb found"
fi
As already pointed out, you don't need piping a var in this case. But just in case you needed it in some other situation, you can use
xargs rm <<< $filesToDelete
or, more portably
echo $filesToDelete | xargs rm
Beware of spaces in file names.
To also output the value together with piping it, use tee with process substitution:
echo "$x" | tee >( xargs rm )
You can directly use -exec to perform an action on the files that were found in find:
find $1 -type f -size +$2k -exec rm {} \;
The -exec trick makes find execute the command given for each one of the matches found. To refer the match itself we have to use {} \;.
If you want to perform more than one action, -exec sh -c "..." makes it. For example, here you can both print the name of the files are about to be removed... and remove them. Note the f={} thingy to store the name of the file, so that it can be used later on in echo and rm:
find $1 -type f -size +$2k -exec sh -c 'f={}; echo "removing $f"; rm $f' {} \;
In case you want to print a message if no matches were found, you can use wc -l to count the number of matches (if any) and do an if / else condition with it:
if [ $(find $1 -type f -size +$2k | wc -l) -ge 1 ]; then
find $1 -type f -size +$2k -exec rm {} \;
else
echo "no matches found"
fi
wc is a command that does word count (see man wc for more info). Doing wc -l counts the number of lines. So command | wc -l counts the number of lines returned by command.
Then we use the if [ $(command | wc -l) -ge 1 ] check, which does an integer comparison: if the value is greater or equal to 1, then do what follows; otherwise, do what is in else.
Buuuut the previous approach was using find twice, which is a bit inefficient. As -exec sh -c is opening a sub-shell, we cannot rely on a variable to keep track of the number of files opened. Why? Because a sub-shell cannot assign values to its parent shell.
Instead, let's store the files that were deleted into a file, and then count it:
find . -name "*.txt" -exec sh -c 'f={}; echo "$f" >> /tmp/findtest; rm $f' {} \;
if [ -s /tmp/findtest ]; then #check if the file is empty
echo "file has $(wc -l < /tmp/findtest) lines"
# you can also `cat /tmp/findtest` here to show the deleted files
else
echo "no matches"
fi
Note that you can cat /tmp/findtest to see the deleted files, or also use echo "$f" alone (without redirection) to indicate while removing. rm /tmp/findtest is also an option, to do once the process is finished.
You don't need to do all this. You can directly use find command to get the files over a particular size limit and delete it using xargs.
This should work:
#!/bin/bash
if [ $(find $1 -type f -size +$2k | wc -l) -eq 0 ]; then
echo "No Files to delete"
else
echo "Deleting the following files"
find $1 -size +$2 -exec ls {} \+
find $1 -size +$2 -exec ls {} \+ | xargs rm -f
echo "Done"
fi

Compare files with the same name

I created script to compare files in folder (with the name .jpg and without it BUT with the same NAME).The problem that script searches for files in ONE directory ,not in SubDirectories!How i can fix it?
for f in *
do
for n in *.jpg
do
tempfile="${n##*/}"
echo "Processing"
echo "${tempfile%.*}"
echo "$f"
if [[ "${tempfile%.*}" = $f ]]
then
echo "This files have the same name!"
//do something here
else
echo "No files"
fi
done
done
This requires bash version 4 for associative arrays.
shopt -s globstar nullglob extglob
declare -A jpgs
for jpg in **/*.jpg; do
name=$(basename "${jpg%.jpg}")
jpgs["$name"]=$jpg
done
for f in **/!(*.jpg); do
name=$(basename "$f")
if [[ -n ${jpgs["$name"]} ]]; then
echo "$f has the same name as ${jpgs["$name"]}"
fi
done
You can also try using find
find . -type f -name "*.sh" -printf "%f\n" | cut -f1 -d '.' > jpg.txt
while read line
do
find . -name "$line.*" -print
done < jpg.txt

Resources