views:

6762

answers:

7

I'm a total newbie to doing any bash scripting, but I came up with a basic one to help automate the process of removing a number of folders as they become unneeded.

#!/bin/bash
rm -rf ~/myfolder1/$1/anotherfolder
rm -rf ~/myfolder2/$1/yetanotherfolder
rm -rf ~/myfolder3/$1/thisisafolder

This is evoked like so:

./myscript.sh <{id-number}>

The problem is that if you forget to type in the id-number (as I did just then), then it could potentially delete a lot of things that you really don't want deleted.

Is there a way you can add any form of validation to the command line parameters? In my case, it'd be good to check that a) there is one parameter, b) it's numerical, and c) that folder exists; before continuing with the script.

A: 

Use '-z' to test for empty strings and '-d to check for directories.

if [[ -z "$@" ]]; then
    echo >&2 "You must supply an argument!"
    exit 1
elif [[ ! -d "$@" ]]; then
    echo >&2 "$@ is not a valid directory!"
    exit 1
fi
guns
+7  A: 
#!/bin/sh
die () {
    echo >&2 "$@"
    exit 1
}

[ "$#" -eq 1 ] || die "1 argument required, $# provided"
echo $1 | grep -E -q '$[0-9]+^' || die "Numeric argument required, $1 provided"

while read dir 
do
    [ -d "$dir" ] || die "Directory $dir does not exist"
    rm -rf "$dir"
done <<EOF
~/myfolder1/$1/anotherfolder 
~/myfolder2/$1/yetanotherfolder 
~/myfolder3/$1/thisisafolder
EOF

edit: I missed the part about checking if the directories exist at first, so I added that in, completing the script. Also, have addressed issues raised in comments; fixed the regular expression, switched from == to eq.

This should be a portable, POSIX compliant script as far as I can tell; it doesn't use any bashisms, which is actually important because /bin/sh on Ubuntu is actually dash these days, not bash.

Brian Campbell
like it for compactness
ojblass
remember to set +e and use '-eq' instead of '==' for integer comparisons
guns
Changed it to -eq; what does set +e buy you here?
Brian Campbell
i found two things in my answer that you may also want to fix in yours: first the SO hilighter goes crazy for $# (treating it as a commentar). i did "$#" to fix it. second, the regex also matches "foo123bar". i fixed it by doing ^[0-9]+$. you may also fix it by using grep's -x option
Johannes Schaub - litb
@litb Thanks, fixed.
Brian Campbell
You lost my vote for compactness...
ojblass
@ojblass I was missing one of the tests he was asking about. Adding that in meant also adding in his directories to test against, which significantly expanded the size of the answer since they can't fit on one line. Can you suggest a more compact way of testing for the existence of each directory?
Brian Campbell
@Brian I am not knocking your clarifications but tailoring your answer to his specific case makes the answer less 'oh cool I can grab that use it and understand it quickly`.
ojblass
guns
@ojblass Fair enough; you can still grab the part from before the while loop as generic code, but the while loop is rather special case for answering this question, and I was trying to answer the full question.
Brian Campbell
Brian Campbell
@Brian Campbell Yes, that's true, but it still does raise the ERR signal inside of functions. I know that's not how you presented it, so I should have qualified my comment.
guns
@guns Hmm, I'm not quite following you. I'm actually relatively new to shell scripting beyond basic command line use and .bashrc, so I'm trying to understand more than argue. In what case will an inherited set -e break this script, or are you just saying that set +e is a good habit to be in?
Brian Campbell
@Brian Campbell I looked into it and found that I had a misunderstanding: echo DONE' fails to echo DONE if set -e is set. This was never a problem with your script.
guns
+1  A: 

Not as bulletproof as the above answer, however still effective:

#!/bin/bash
if [ "$1" = "" ]
then
  echo "Usage: $0 <id number to be cleaned up>"
  exit
fi

# rm commands go here
Boiler Bill
A: 

Check out the man page for test (man test) which should give you every available operator you can use when checking the values of any variables/strings/expressions you have. Use this in the beginning of your script (or functions) for input validation just like you would in any other programming language.

whaley
+2  A: 

I would use bash's [[:

if [[ ! ("$#" == 1 && $1 =~ ^[0-9]+$ && -d $1) ]]; then 
    echo 'Please pass a number that corresponds to a directory'
    exit 1
fi

I found this faq to be a good source of information.

Johannes Schaub - litb
+2  A: 

The sh solution by Brian Campbell, while noble and well executed, has a few problems, so I thought I'd provide my own bash solution.

The problems with the sh one:

  • The tilde in ~/foo doesn't expand to your homedirectory inside heredocs. And neither when it's read by the read statement or quoted in the rm statement. Which means you'll get No such file or directory errors.
  • Forking off grep and such for basic operations is daft. Especially when you're using a crappy shell to avoid the "heavy" weight of bash.
  • I also noticed a few quoting issues, for instance around a parameter expansion in his echo.
  • While rare, the solution cannot cope with filenames that contain newlines. (Almost no solution in sh can cope with them - which is why I almost always prefer bash, it's far more bulletproof & harder to exploit when used well).

While, yes, using /bin/sh for your hashbang means you must avoid bashisms at all costs, you can use all the bashisms you like, even on Ubunutu or whatnot when you're honest and put #!/bin/bash at the top.

So, here's a bash solution that's smaller, cleaner, more transparent, probably "faster", and more bulletproof.

[[ -d $1 && $1 != *[^0-9]* ]] || { echo "Invalid input." >&2; exit 1; }
rm -rf ~/foo/"$1"/bar ...
  1. Notice the quotes around $1 in the rm statement!
  2. The -d check will also fail if $1 is empty, so that's two checks in one.
  3. I avoided regular expressions for a reason. If you must use =~ in bash, you should be putting the regular expression in a variable. In any case, globs like mine are always preferable and supported in far more bash versions.
lhunath
+1  A: 

You can validate point a and b compactly by doing something like the following:

#!/bin/sh
MYVAL=$(echo ${1} | awk '/^[0-9]+$/')
MYVAL=${MYVAL:?"Usage - testparms <number>"}
echo ${MYVAL}

Which gives us ...

$ ./testparams.sh 
Usage - testparms <number>

$ ./testparams.sh 1234
1234

$ ./testparams.sh abcd
Usage - testparms <number>

This method should work fine in sh.

MattK