Unix Bash Shell Scripting File Size - bash

I am working on a bash shell script which requires me to display files in order of size in a given directory. If the size of a file is 0, I am to ask the user if they would like to delete it. So far I have this:
#!/bin/bash
FILE=$(ls -S $1)
for FIL in ${FILE}
do
echo ${FIL}
done
This displays the files in order of size, but I am unsure how to prompt the user to erase the files with a size of 0.
Thanks for your help!

find /your/path/ -size 0 -exec echo rm -i {} \; # will fail if there are spaces in any file names
better way:
find /your/path/ -size 0 -print0 | xargs -0 rm -i
Remove the echo to delete the files
Thanks #Will, #AdamKatz.

So if we want to stay as close as possible to your current approach, we could do it like this:
#!/bin/bash
FILE="$(ls -S "$1")"
for f in $FILE
do
file_size_bytes=$(du "$f" | cut -f1)
echo "$f"
if [[ "$file_size_bytes" -eq 0 ]]
then
read -r -p "Would you like to delete the zero-byte file ${f}? [Y/n]: " input
if [[ "$input" = [Yy] ]]
then
rm "$f"
fi
fi
done
Another answer used stat, but stat isn't POSIX or portable, but if you're only running under Linux, stat is a good approach.
In the above example, read -p is used to prompt the user for input, and store the result in $input. We use [[ "$input" = [Yy] ]] to see if the input is either Y or y.
The way it's currently written, you have to type y or Y and press enter to delete the file. If you want it to happen as soon as the user hits y or Y, add -n 1 to read to make it only read one character.
You also don't need to use ${var} unless you're putting it inside another string, or if you need to use some kind of parameter expansion.
As a side note, this sounds like it's some type of homework or learning experience, so, please look up every command, option, and syntax element in the above and really learn how it works.

You can make use of redirection and redirect stdin to another file descriptor while feeding the loop with process substitution to accomplish your goal. e.g.:
#!/bin/bash
[ -z "$1" ] && {
printf "error: insufficient input, usage: %s <path>\n" "${0//*\/}"
exit 0;
}
exec 3<&0 # temprorary redirection of stdin to fd 3
while read -r line; do
printf " rm '%s' ? " "$line"
read -u 3 ans # read answer for fd 3
anslower="${ans,,}"
if [ "${anslower:0:1}" = "y" ]; then
printf " %s => removed.\n" "$line"
# rm "$line"
else
printf " %s => unchanged.\n" "$line"
fi
done < <(find "$1" -type f -size 0)
exec 3<&- # close temporary redirection
note: the actual rm command is commented out to insure you don't remove wanted files by accident until your testing is complete.
Example Use/Output
$ bash findzerosz.sh ../tmp/stack/dat/tmp/tst/tdir
rm '../tmp/stack/dat/tmp/tst/tdir/file4.html' ? n
../tmp/stack/dat/tmp/tst/tdir/file4.html => unchanged.
rm '../tmp/stack/dat/tmp/tst/tdir/file1.html' ? y
../tmp/stack/dat/tmp/tst/tdir/file1.html => removed.
rm '../tmp/stack/dat/tmp/tst/tdir/file2.html' ? y
../tmp/stack/dat/tmp/tst/tdir/file2.html => removed.
rm '../tmp/stack/dat/tmp/tst/tdir/file3.html' ? Y
../tmp/stack/dat/tmp/tst/tdir/file3.html => removed.
rm '../tmp/stack/dat/tmp/tst/tdir/file5.html' ? n
../tmp/stack/dat/tmp/tst/tdir/file5.html => unchanged.

This will work, to test if one file size is 0 (you just need to include it in your loop).
myfilesize=`stat -c %s "$FIL"`
if [ $myfilesize = 0 ];then
echo "the file size is zero, do you want to delete it ?"
read -p "yes/no? " -n 1 -r
echo #Move to Next line
if [[ $REPLY =~ ^[Yy]$ ]]
then
rm "$FIL"
fi
else
echo "File size is not Zero"
fi

Related

bash string length in a loop

I am looping through a folder and depending on the length of files do certain condition. I seem not to come right with that. I evaluate and output the length of a string in the terminal.
echo $file|wc -c gives me the answer of all files in the terminal.
But incorporating this into a loop is impossible
for file in `*.zip`; do
if [[ echo $file|wc -c ==9]]; then
some commands
where I want to operate on files that have a length of nine characters
Try this one:
for file in *.zip ; do
wcout=$(wc -c "$file")
if [[ ${wcout%% *} -eq 9 ]] ; then
# some commands
fi
done
The %% operator in variable expansion deletes everything that match the pattern after it. This is glob pattern, not regular expression.
Opposite to natural good sense of typical programmers the == operator in BASH compares strings, not numbers.
Alternatively (following the comment) you can:
for file in *.zip ; do
wcout=$(wc -c < "$file")
if [[ ${wcout} -eq 9 ]] ; then
# some commands
fi
done
Additional observation is that if BASH cannot expand *.zip as there is no ZIP files in the current directory it will pass "*.zip" into $file and let single iteration of the loop. That leads to the error reported by wc command. So it would be recommended to add:
if [[ -e ${file} ]] ; then ...
as a prevention mechanism.
Comments leads to another form of this solution (plus I added my safety mechanism):
for file in *.zip ; do
if [[ -e "$file" && (( $(wc -c < "$file") == 9 )) ]] ; then
# some commands
fi
done
using filter outside the loop
ls -1 *.zip \
| grep -E '^.{9}$' \
| while read FileName
do
# Your action
done
using filter inside loop
ls -1 *.zip \
| while read FileName
do
if [ ${#FileName} -eq 9 ]
then
# Your action
fi
done
alternative to ls -1 that is always a bit dangereous, find . -name '*.zip' -print [ but you neet to add 2 char length or filter the name form headin ./ and maybe limit to current folder depth ]

How to list files with words exceeding n characters in all subdirectories

I have to write a shell script that creates a file containing the name of each text files from a folder (given as parameter) and it's subfolders that contain words longer than n characters (read n from keyboard).
I wrote the following code so far :
#!/bin/bash
Verifies if the first given parameter is a folder:
if [ ! -d $1 ]
then echo $1 is not a directory\!
exit 1
fi
Reading n
echo -n "Give the number n: "
read n
echo "You entered: $n"
Destination where to write the name of the files:
destinatie="destinatie"
the actual part that i think it makes me problems:
nr=0;
#while read line;
#do
for fisier in `find $1 -type f`
do
counter=0
for word in $(<$fisier);
do
file=`basename "$fisier"`
length=`expr length $word`
echo "$length"
if [ $length -gt $n ];
then counter=$(($counter+1))
fi
done
if [ $counter -gt $nr ];
then echo "$file" >> $destinatie
fi
done
break
done
exit
The script works but it does a few more steps that i don't need.It seems like it reads some files more than 1 time. If anyone can help me please?
Does this help?
egrep -lr "\w{$n,}" $1/* >$destinatie
Some explanation:
\w means: a character that words consist of
{$n,} means: number of consecutive characters is at least $n
Option -l lists files and does not print the grepped text and -r performs a recursive scan on your directory in $1
Edit:
a bit more complete version around the egrep command:
#!/bin/bash
die() { echo "$#" 1>&2 ; exit 1; }
[ -z "$1" ] && die "which directory to scan?"
dir="$1"
[ -d "$dir" ] || die "$dir isn't a directory"
echo -n "Give the number n: "
read n
echo "You entered: $n"
[ $n -le 0 ] && die "the number should be > 0"
destinatie="destinatie"
egrep -lr "\w{$n,}" "$dir"/* | while read f; do basename "$f"; done >$destinatie
This code has syntax errors, probably leftovers from your commented-out while loop: It would be best to remove the last 3 lines: done causes the error, break and exit are unnecessary as there is nothing to break out from and the program always terminates at its end.
The program appears to output files multiple times because you just append to $destinatie. You could simply delete that file when you start:
rm "$destinatie"
You echo the numbers to stdout (echo "$length") and the file names to $destinatie (echo "$file" >> $destinatie). I do not know if that is intentional.
I found the problem.The problem was the directory in which i was searching.Because i worked on the files from the direcotry and modified them , it seems that there remained some files which were not displayed in file explorer but the script would find them.i created another directory and i gived it as parameter and it works. Thank you for your answers
.

While loop does not execute

I currently have this code:
listing=$(find "$PWD")
fullnames=""
while read listing;
do
if [ -f "$listing" ]
then
path=`echo "$listing" | awk -F/ '{print $(NF)}'`
fullnames="$fullnames $path"
echo $fullnames
fi
done
For some reason, this script isn't working, and I think it has something to do with the way that I'm writing the while loop / declaring listing. Basically, the code is supposed to pull out the actual names of the files, i.e. blah.txt, from the find $PWD.
read listing does not read a value from the string listing; it sets the value of listing with a line read from standard input. Try this:
# Ignoring the possibility of file names that contain newlines
while read; do
[[ -f $REPLY ]] || continue
path=${REPLY##*/}
fullnames+=( $path )
echo "${fullnames[#]}"
done < <( find "$PWD" )
With bash 4 or later, you can simplify this with
shopt -s globstar
for f in **/*; do
[[ -f $f ]] || continue
path+=( "$f" )
done
fullnames=${paths[#]##*/}

Bash Shell Script: Diff command conflicting line format

I am using Windows OS.
My program:
#!/bin/bash
OIFS="$IFS"
IFS=$'\n'
find teste1 -type f | while read -r firstResult
do
find teste2 -type f | while read -r secondResult
do
firstName=${firstResult##*[/|\\]}
secondName=${secondResult##*[/|\\]}
if [[ "$( echo "$firstName" | tr [A-Z] [a-z])" == "$( echo "$secondName" | tr [A-Z] [a-z])" ]]; then
echo "$firstResult" "$secondResult" >> equal
else
echo "$firstResult" "$secondResult" >> notEqual
fi
if [[ $firstName == $secondName ]]; then
echo "$firstResult" "$secondResult" >> equal2
fi
done
done
diff -2 "--line-format=%L" "--unchanged-line-format=" equal equal2 > renamedFiles.lst
rm equal
rm equal2
rm notEqual
Whenever I run this program, it says "diff: conflicting line format". However, it produces the "renamedFiles.lst" and produces exactly the way I want. So, why is it giving me this answer? Can I fix it? It doesn't really affect my program but no one likes seeing warnings / errors on their programs, right? :)
I think it's because you are using both --line-format (to format all lines) and --unchanged-line-format (to format unchanged lines). I guess diff doesn't define what to do if it gets conflicting format specifiers, so it fails and tells you about it. What you could do is use for example --old-line-format=%L --new-line-format=%L --unchanged-line-format=

How to test filename expansion result in bash?

I want to check whether a directory has files or not in bash.
My code is here.
for d in {,/usr/local}/etc/bash_completion.d ~/.bash/completion.d
do
[ -d "$d" ] && [ -n "${d}/*" ] &&
for f in $d/*; do
[ -f "$f" ] && echo "$f" && . "$f"
done
done
The problem is that "~/.bash/completion.d" has no file.
So, $d/* is regarded as simple string "~/.bash/completion.d/*", not empty string which is result of filename expansion.
As a result of that code, bash tries to run
. "~/.bash/completion.d/*"
and of course, it generates error message.
Can anybody help me?
If you set the nullglob bash option, through
shopt -s nullglob
then globbing will drop patterns that don't match any file.
# NOTE: using only bash builtins
# Assuming $d contains directory path
shopt -s nullglob
# Assign matching files to array
files=( "$d"/* )
if [ ${#files[#]} -eq 0 ]; then
echo 'No files found.'
else
# Whatever
fi
Assignment to an array has other benefits, including desirable (correct!) handling of filenames/paths containing white-space, and simple iteration without using a sub-shell, as the following code does:
find "$d" -type f |
while read; do
# Process $REPLY
done
Instead, you can use:
for file in "${files[#]}"; do
# Process $file
done
with the benefit that the loop is run by the main shell, meaning that side-effects (such as variable assignment, say) made within the loop are visible for the remainder of script. Of course, it's also way faster, if performance is an issue.
Finally, an array can also be inserted in command line arguments (without splitting arguments containing white-space):
$ md5sum fileA "${files[#]}" fileZ
You should always attempt to correctly handle files/paths containing white-space, because one day, they will happen!
You could use find directly in the following way:
for f in $(find {,/usr/local}/etc/bash_completion.d ~/.bash/completion.d -maxdepth 1 -type f);
do echo $f; . $f;
done
But find will print a warning if some of the directory isn't found, you can either put a 2> /dev/null or put the find call after testing if the directories exist (like in your code).
find() {
for files in "$1"/*;do
if [ -d "$files" ];then
numfile=$(ls $files|wc -l)
if [ "$numfile" -eq 0 ];then
echo "dir: $files has no files"
continue
fi
recurse "$files"
elif [ -f "$files" ];then
echo "file: $files";
:
fi
done
}
find /path
Another approach
# prelim stuff to set up d
files=`/bin/ls $d`
if [ ${#files} -eq 0 ]
then
echo "No files were found"
else
# do processing
fi

Resources