2

I'm coding a very simple shell that deletes files of a given folder, in this case all .csv files that lives in that folder.

The code looks like this:

#!/bin/bash
# Used to clean folder

MinVal=0
Files=$(ls -1 *.csv | wc -l)


if [ $Files -gt $MinVal ];then
        rm *.csv
        echo -e "\e[31;43m***** DONE *****\e[0m"    

 else       
        echo -e "\e[31;43mThere is no valid file to delete, please check\e[0m"      


fi

The shell script works as expected, but when you execute the shell and there is no .csv files in the folder the terminal says the following:

ls: cannot access *.csv: No such file or directory

off course it also shows the else message:

There is no valid file to delete, please check

Is there any way to avoid the ls: cannot access *.csv: No such file or directory and get straight to the to the else error handling?

Greetings.

  • What should happen if there are .csv files but they couldn't be deleted (e.g. permissions)? Right now your script just will say "DONE" as if all is well. There are other less common but possible errors, like if a directory called foo.csv exists, rm will try to delete it but will fail. – Brandin Nov 8 '17 at 14:37
  • It is a possibility for sure but for this scenario there's no need to go that deep, It's outside of the scope – sandatomo Nov 8 '17 at 14:40
  • 2
    Looking at your rm -r as well as your comment, chances are you want simply a recursive deletion: find . -type f -iname '*.csv' -delete Also printing things like "DONE" is against Unix convention and a bad idea. – kubanczyk Nov 8 '17 at 17:25
8

This happens because *.csv is passed verbatim to ls by the shell, which is default POSIX behaviour. If you want to avoid this, you should use nullglob so that globs that return nothing return nothing instead of returning the verbatim string entered. This has a problem though, because ls has different behaviour when no files are provided.

Try this instead, which has the benefit of avoiding bugs in the case of embedded newlines in filenames:

shopt -s nullglob
filenames=( *.csv )  # put all files in an array
files="${#filenames[@]}"  # get number of files
  • I haven't use arrays yet but I will give that a try, It makes the code very elegant, thank you – sandatomo Nov 8 '17 at 14:25
  • 1
    How do people remember these bizarre BASH constructions? They seem like magical incantations that would need to be googled each time you want to use them. – BallpointBen Nov 8 '17 at 22:47
  • 1
    @BallpointBen The same way you learn any other language, by using it a lot :-) Bash is not perfect, but it's not completely unintelligible either. It ended up this way for a bunch of reasons. – Chris Down Nov 9 '17 at 0:42
5

If all you want to do is suppress the warning message from ls then the most straight-forward thing to do would be to redirect stderr to /dev/null, e.g.:

Files=$(2>/dev/null ls -1 *.csv | wc -l)

Note that redirecting stderr will suppress all errors resulting from this command, including errors that may not be related to the absence of *.csv files in the directory.

A better solution for counting files might be to use find instead of ls, e.g.:

find . -maxdepth 1 -name "*.csv" | wc -l

In fact, you might want to use find to perform the deletion as well:

find . -maxdepth 1 -name "*.csv" -delete

I also noticed that you used the -r flag with rm, which looks like it may be unintentional, since you're performing a flat search using ls followed by a recursive delete with rm.

Frankly though, I'm not sure that it makes a lot of sense to write a script for this task. What benefit does this script offer over just running the commands directly? In other words, what's wrong with this:

# Check for files
ls *.csv

# Delete the files
rm *.csv

Why is running your script better than running these commands? It looks like you're just adding overhead and replacing one error message with another.

  • 1
    This will also suppress other errors -- for example, if the current cwd is invalid, there are I/O errors, etc. – Chris Down Nov 8 '17 at 14:16
  • Hi, thanks you for the information, actually your first solution works pretty well, I will try the find at home because that command doesn't exist in the production server. – sandatomo Nov 8 '17 at 14:23
  • @ChrisDown You're right, of course. But I feel pretty strongly that the first part of a response should always answer the question as asked and provide the simplest and most direct possible solution. Any additional caveats, subtlety, etc. should follow that first simple solution. Personally I think that if someone is trying to write a program that has robust error-handling then they probably shouldn't be doing it in pure Bash anyway. Anyway, thanks for the comment. I'll add a note - upvoted. – igal Nov 8 '17 at 14:30
  • @sandatomo, if this is for a production script, please just use the rm *.csv command. And consider whether you want the dotglob option set. – Wildcard Nov 8 '17 at 22:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.