3

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!

1

4 Answers 4

3
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.

Sign up to request clarification or add additional context in comments.

13 Comments

This doesn't answer the prompting.
Well I suppose the same answer with xargs would work : find /your/path/ -size 0 | xargs -p -I {} rm -f {} \;
@Will Could just use rm -i?
Would I be able to use this method with the FIL in the for loop instead of the path? I tried replacing the path with FIL and I'm getting a 'conditional binary operator expected' error.
#!/bin/bash FILE=$(ls -S $1) for FIL in $FILE do if [[ find FIL -size 0 ]] then echo zero fi done
|
3

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.

5 Comments

I am not seeing the -b option for du in the man page. I should have specified the OS, I'm running El Capitan on my Mac.
Ah, I have it on my Yosemite Mac; strange. You can just take off the -b and it will work just the same; "bytes" is the default behavior". I just took it off in my answer. Without the -b would be more portable anyway. Good catch!
I removed the -b and am no longer seeing the error but my screen is overrun with integers when I run the script. I removed the nested if so I could get the outer if to work first. In the outer if after the 'then' line, I added 'echo zero'. I can't figure out where these numbers are coming from :/
What happens if you have spaces in your filename with for f in $FILE? Perhaps look at setting IFS?
Well, OP parsed output from ls; I don't. shellcheck warned me better than that ;) But the goal is to make minimal changes to OP's solution. If it were me, I'd also just get the size and the filenames with the same command pipeline, then parse it in a IFS= while read line; ....
2

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.

1 Comment

Can add something like -printf '%s %p\n' | sort -rn to match the sort aspect question.
0

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

2 Comments

When I try this I am getting an 'illegal option -- c' error at the first line and a 'unary operator expected' error at the line with the first if statement. When I run a man on stat I'm not seeing the -c option displayed.
@tfreiner What Operating System is this? Does --printf "%s" work? But, yeah, stat isn't very portable and is mainly a Linux-thing. That's why I chose du -b; it's POSIX! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.