1

I'm trying to create a shell script file that uses getopts. The purpose of the program is to remove files that are in the project and place them in the deleted directory aka recycling bin. I have already been able to successfully complete this.

I have also been able to use the getopt command -i (interactive) to pass into a case statement and then trigger off a question for the user asking if they are sure they would like to remove a file.

This has been done by creating a variable "ision" and setting it to true when the case statement is activated. I have inserted my code down below. I've tried it out a couple of times and everything seems to be fine but I would like to add the verbose activity/mode. Is there anyone who could help me out?

#!/bin/bash

while getopts ":i:v" option ;
do
case "$option" in
i) echo "interactive mode set"
ision=true;
break;;
v) echo "verbose mode"
vison=true;
break;;
esac
done
echo "this is the proof we need"

echo $@

shift $(($OPTIND-2))

echo $@

echo "this is working too"

if [ ! -e ~/deleted ]
then
mkdir ~/deleted
fi

echo "the echo file was made or just created"

if [ $# -eq 0 ]
then
echo "safe_rm missing operand"
exit
fi

echo "all workking on the western front"

for i in $@
do

if [ "$ision" == "true" ]
then
echo "do you want to remobve the file"
echo "variable test $i"
echo "yes or no"
read -p "Enter " answer
if [ "$answer" == "no" ]
then
continue;
fi
fi

if [ ! -f $i ]
then
echo "no such file or directory"
exit
fi
        if [ $i == safe_rm ]
        then

        echo "cant remove safe_rm"
        continue
        fi

        if [ $i == safe_rm_restore ]
        then
        continue
        echo "cant remove safe_rm_restore"

        fi

inode=$(ls -i $i | cut -c -6 )
echo "the inode is $inode "
pathname=$(dirname $i)
if [ $pathname == "." ]
then
pathname=$(pwd)
echo $pathname
fi
basename=$(basename $i)
path=$basename"_"$inode":"$pathname"/"$basename

echo $path

if [ ! -f .restoreinfo ]
then
touch .restoreinfo
fi


echo $path >> .restoreinfo
mv $i ~/deleted/

done
1

I'm going to concentrate on the command line parsing here:

# defaults:
ision=0
vison=0

while getopts "iv" option; do
    case "$option" in
        i)
            echo "interactive mode set"
            ision=1 ;;
        v)
            echo "verbose mode"
            vison=1 ;;
        *)
            exit 1 ;;
    esac
done
shift $(( OPTIND - 1 ))

(( vison )) && echo 'This is a verbose message'

if (( ision )); then
   # interactive code
fi

It's much easier to operate on ision and vison if they are integers. Then you can just test their value with (( vison )) as I've shown above.

You also got the command option string of getopts wrong. As far as I can tell, neither option takes an argument, which means there should not be any colons in the string. If an option takes an argument, the colon should come after it, as in v:.

Don't break when you're parsing the command line, that will stop the parsing of it. Instead, just set the flags and do whatever else you need doing and let the loop continue. I'm assuming you may have some familiarity with C where one does need to break from a case statement (in a switch). It's not the same here.

Don't do output while parsing the command line, except as a debugging aid. Many utilities have flags that cancel each other, e.g. -v for verbose mode followed by -q for "quiet" mode, and having the code output "entering verbose mode" followed by "entering quiet mode" is just noisy.

By the way, to do something like that (handling mutually exclusive options), you could have

case "$option" in
    q)
        quiet=1
        verbose=0 ;;
    v)
        verbose=1
        quiet=0   ;;

    # etc.

When the user uses -qv, quiet will first be set to 1 and verbose be set to 0, and then the values will be reversed. There should be no error reported in this case as you don't know if the command line was put together manually or by a calling script.

I've inserted an exit 1 statement that will be executed if an unknown command line option is given. This may be good to have as it means the user has made an error and it may be dangerous to blindly trust what's on the command line from there on.

The shift (to shift off the parsed options) should be done as above. Shifting $OPTIND - 2 would shift one too many things off the argument list.

I've also improved on the indentation, which makes it easier to read, and therefore easier to debug and maintain.


As for the rest of the script, I haven't looked too closely at it, but I notice a lot of unquoted variable expansions. Don't do that as it will mess up things if you get given filenames with spaces (or other whitespace characters) in them.

Especially $@ needs to be quoted when used in e.g. loops. This prevents the command line arguments from being split on whitespaces (the contents of $IFS to be more precise), and it avoids accidental file name globbing if the names contain globbing pattern characters.

There are also quite a number of continue statements. IMHO, these should be eliminated as they make it hard to follow the flow of the code (especially when the code is not indented properly). They are not wrong in themselves but they could be removed if the code handles the various conditions appropriately. In at least one case, you have an echo directly after a continue statement, which means that this echo will never be triggered:

    continue
    echo "cant remove safe_rm_restore"
  • 1
    Note that a leading : in the option specification is to disable error messages (and store the option causing failure in $OPTARG), it's not related to option arguments. – Stéphane Chazelas Sep 15 '17 at 9:57
0

You can use some shorthand comparisons. This is an if/then/else statement listed off in a way that is similar to the ?: notation in C and several other languages. You can run the comparison and use an && to run a block if true, and a || to run a block if false.

$ vison=true 
$ [[ "$vison" == "true" ]] && vison=false || vison=true
$ echo $vison
false
$ [[ "$vison" == "true" ]] && vison=false || vison=true
$ echo $vison
true

The same thing can be done to check for verbose logging too.

[[ "$vison" == "true" ]] && echo "this is shown only in verbose mode"
  • Do note that if the condition is true and the "then part of the branch" evaluates to false, the else part will be evaluated. The semantics aren't exactly the same as an if-then-else so care should be taken. – ctt Jun 12 '14 at 1:14
0

There are false and true commands that are handy to express booleans in shells. For verbosity, a common approach is to use a log level where each occurrence of -v increases the log level and -q decreases it.

interactive=false
log_level=1

log() {
  local level="$1"
  if [ "$level" -ge "$log_level" ]; then
    shift
    local IFS=" "
    printf '%s\n' "$*"
  fi
}


while getopts iqv option; do
    case $option in
        i)
            log 2 "interactive mode set"
            interactive=true;;
        v)
            log 2 "increasing log level"
            log_level=$((log_level + 1));;
        q)
            log 2 "decreasing log level"
            log_level=$((log_level - 1));;
        *)
            exit 1 ;;
    esac
done
shift "$((OPTIND - 1))"

log 2 'This is a verbose message'
log 3 'This is a debug message'

if "$interactive"; then
   # interactive code
fi

# or:

ask() { # args: var default question
  if "$interactive"; then
    printf %s "$3"
    IFS= read -r "$1"
  else
    eval "$1=\$2"
  fi
}

yesno() { # args: default question
  local answer="$1"
  ask answer "$1" "$2"
  case $answer in
    ([yY][eE][sS] | y | Y) return 0;;
    ([nN][oO] | n | N) return 1;;
    (*) case $1 in
          ([yY][eE][sS] | y | Y) return 0;;
          ([nN][oO] | n | N) return 1;;
          (*) log >&2 -1 "Wrong default value $default"; exit 1;;
        esac;;
  esac
}

if yesno no "Are you OK with that (yes/[no])? "; then
   log 1 OK do it
   ...
fi

The code above (untested) is Debian-policy compliant so will work with Debian sh whether it's configured to be dash, lksh or bash (would also be POSIX if not for local).

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.