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!

tfreiner
  • 209
  • 2
  • 13

4 Answers4

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.

Will
  • 24,082
  • 14
  • 97
  • 108
  • 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. – tfreiner Feb 20 '16 at 23:35
  • 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! – Will Feb 20 '16 at 23:42
  • 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 :/ – tfreiner Feb 21 '16 at 00:08
  • What happens if you have spaces in your filename with `for f in $FILE`? Perhaps look at setting `IFS`? – David C. Rankin Feb 21 '16 at 00:11
  • 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; ...`. – Will Feb 21 '16 at 21:52
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.

Wanming Zhang
  • 323
  • 1
  • 8
  • 1
    This doesn't answer the prompting. – Will Feb 20 '16 at 23:19
  • Well I suppose the same answer with xargs would work : find /your/path/ -size 0 | xargs -p -I {} rm -f {} \; – Francois Feb 20 '16 at 23:21
  • 1
    @Will Could just use `rm -i`? – Benjamin W. Feb 20 '16 at 23:45
  • 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. – tfreiner Feb 20 '16 at 23:59
  • #!/bin/bash FILE=$(ls -S $1) for FIL in $FILE do if [[ find FIL -size 0 ]] then echo zero fi done – tfreiner Feb 21 '16 at 00:13
  • It's not keeping my formatting for some reason sorry – tfreiner Feb 21 '16 at 00:13
  • compare the file size with zero, you should use `stat` or `-s`. e.g.: `if [ ! -s $FIL ]` or `if [ $(stat -f '%z' $FIL) -eq 0 ]` – Wanming Zhang Feb 21 '16 at 00:21
  • Error with `.. if [[ find FIL -size 0 ]] .. `: You wanted to write `$FIL`. FIL can have spaces, so safer is `"$FIL"`. Get used to lowercase variables and braces like `"${fil}"`, this will avoid errors in the future. – Walter A Feb 21 '16 at 10:14
  • This solution will fail if there are spaces in any file names. Try `find /your/path -size 0 -print0 |xargs -0 rm -i` (also note that [`xargs` is faster than `find -exec`](https://stackoverflow.com/questions/980411/which-is-faster-find-exec-or-find-xargs-0)). – Adam Katz Feb 21 '16 at 20:54
  • I like @WanmingZhang's idea to compare the file to 0 with ! -s or stat -f. However when I try ! -s it treats all the files as if they have a size of 0, and when I try stat -f I receive a 'no such file or directory error' for every file. – tfreiner Feb 22 '16 at 03:20
  • @AdamKatz No it won't. Also, `-exec ... +` is not slower than `xargs` – Reinstate Monica Please Mar 02 '16 at 01:20
  • @WanmingZhang `{}` is not a variable, it's not expanded by `bash`, but by `find`. So your first command will work perfectly fine if the filename has spaces. – Reinstate Monica Please Mar 02 '16 at 01:37
  • Agreed, `-exec` works fine with filenames that have spaces. I was wrong there. However, I'm not wrong about the speed: `find -exec` invokes the external command once per file while `xargs` will lump them together (see my link). This will be significantly faster on a very large list of files. – Adam Katz Mar 02 '16 at 02:20
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.
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
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
Francois
  • 515
  • 4
  • 14
  • 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 Feb 20 '16 at 23:25
  • @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! :) – Will Feb 20 '16 at 23:28