Certain command line options not working

I've written a (poor) script for displaying information about ports, but one of the command line options is not working as expected.

When I specify the option "-m", I get the message
Code:
[: -m: unexpected operator
followed by the expected output:

% ./pdesc.sh -m wmii
Code:
[: -m: unexpected operator
devel/libixp
x11/bigreqsproto
x11/xcmiscproto
x11/xtrans
x11-fonts/xf86bigfontproto
devel/libcheck
x11/xcb-proto
textproc/libxslt
security/libgcrypt
security/libgpg-error

All the command line options have been set the same way (no doubt not the best way) but only this option misbehaves.

Here is the script in full:
Code:
#!/bin/sh

# This script will allow users to search the ports tree and display port categories, ports names and port descriptions.

# Set a few useful variables
pdir="/usr/ports"

# Usage:
# pdesc [-h] - print this help
# pdesc [-c [category name]] - list all categories, or contents therein
# pdesc [-n port-name] - display pkg-descr contents for given port
# pdesc [-s string] - search for given port
# pdesc [-d port-name] - print all dependencies for given port
# pdesc [-b port-name] - print build dependencies for given port
# pdesc [-m port-name] - print mising dependencies for given port

if [ -z $1 ]
then
    echo "Usage:
    pdesc [-h] - print this help
    pdesc [-c [category name]] - list all categories, or contents therein
    pdesc [-n port-name] - display pkg-descr contents for given port
    pdesc [-s string] - search for given port
    pdesc [-d port-name] - print all dependencies for given port
    pdesc [-b port-name] - print build dependencies for given port
    pdesc [-m port-name] - print mising dependencies for given port
    "
elif [ $1 = -h ]
then 
    echo "Usage:
    pdesc [-h] - print this help
    pdesc [-c [category name]] - list all categories, or contents therein
    pdesc [-n port-name] - display pkg-descr contents for given port
    pdesc [-s string] - search for given port
    pdesc [-d port-name] - print all dependencies for given port
    pdesc [-b port-name] - print build dependencies for given port
    pdesc [-m port-name] - print mising dependencies for given port
    "

elif [ $1 = -c ] && [ -z $2 ]
then
    ls -d $pdir/*/ | sed s:$pdir/:: | column

elif [ $1 = -c ] && [ -d $pdir/$2 ]
then
    ls -d $pdir/$2/*/ | sed s:$pdir/$2/:: | column 

elif [ $1 -n ] && [ -z $2 ]
then
    echo "The -n option requires a valid name of a port."

elif [ $1 = -n ] 
then
    more $pdir/*/$2/pkg-descr

elif [ $1 = -s ] && [ -z $2 ]
then
    echo "The -s options requires a search pattern."

elif [ $1 = -s ] && [ -n $2 ]
then
    find $pdir/* -maxdepth 1 -iname *$2* | sed s:$pdir/:: | column

elif [ $1 = -d ] && [ -z $2 ]
then
    echo "The -d option requires a valid port name."

elif [ $1 = -d ] && [ -n $2 ]
then
    make -C $pdir/*/$2 all-depends-list

elif [ $1 = -b ] && [ -z $2 ]
then
    echo "The -b option requires a valid port name."

elif [ $1 = -b ] && [ -n $2 ]
then

elif [ $1 = -m ] && [ -z $2 ]
then
    echo "The -m option requires a valid port name."

elif [ $1 = -m ] && [ -n $2 ]
then
    make -C $pdir/*/$2 missing
fi
 
Use getopt(1) to parse the command line options or the bit more advanced getopts(1) that is usually a shell builtin. What you now have is unmaintainable because you have to be extra careful to get all those elif then constructs just right.

This is a small example from a script I use:

Code:
while getopts f:j: o
do
    case "$o" in
    f)  PORTSTXT="$OPTARG";;
    j)  BUILD_JAIL="$OPTARG";;
    esac

done

shift $((OPTIND-1))

The -f option takes a name of a file and the -j option takes a name of a jail. This is small wrapper script for building ports with port-mgmt/poudriere-devel.
 
I don't mean to be difficult or a contrarian, but could you explain why double quotes are needed?

I'm asking because I've seen all of the following, and wasn't sure which was best:

Code:
[ $1 -n ]
[ $1 = -n ]
[ "$1" = "-n" ]

They all worked in the script I've given above, so I just went with the one that looked most intuitive for me. Is the reason for double-quoting that it will allow for whitespaces and certain other characters?
 
(That first one is an error, no comparison operator.)

Quotes are needed because the shell is terrible at parsing parameters. When a parameter is not included, it evaluates to nothing, and then test(1) errors out because of the wrong number of parameters. For example:
Code:
#!/bin/sh
echo "\$1=$1"

if [ $1 = a ];then
  echo "first parameter is a"
fi

Running it with a parameter works fine:
Code:
% ./tester.sh a
$1=a
first parameter is a

Now run it without a parameter:
Code:
% ./tester.sh 
$1=
[: =: unexpected operator

The test line evaluated like this:
Code:
if [ = a ]; then

That's not correct, test(1) gets no first parameter, it evaluated to literally nothing. Instead, quote the parameters:
Code:
#!/bin/sh
echo "\$1=$1"

if [ "$1" = "a" ];then
  echo "first parameter is a"
fi

Now run it again:
Code:
% ./tester.sh
$1=

This time it evaluated like expected:
Code:
if [ "" = "a" ];then

Shell input parsing is not good, and in a non-trivial shell script I often find myself having to program around it rather than work on what the script is actually supposed to do. Real programming languages that understand parameters rather than just looking for whitespace are more capable and easier to use.
 
Thanks for the help.

I'll remember to double quote my parameters in the future. I've also taken your advice and learnt how to use a "case statement".

So here is the new version, if you care:
Code:
#!/bin/sh

# This script will allow user to search the ports tree and display port categories, port names, port descriptions and dependency information.

# set some variables
pdir="/usr/ports"

while getopts "hc:Cn:s:d:b:m:r:" opt; do
      case "$opt" in
    h) echo "usage:
    pd.sh [-h] - print this help
    pd.sh [-c category-name] - list category contents
    pd.sh [-C] - list all categories in ports
    pd.sh [-n port-name] - display pkg-descr contents for given port
    pd.sh [-s string] - search for given port
    pd.sh [-d port-name] - print ALL dependencies for given port(s) recursively
    pd.sh [-b port-name] - print build dependencies for given port
    pd.sh [-m port-name] - print mising dependencies for given port
    pd.sh [-r port-name] - print runtime dependencies for given port" >&2 ;;
    c) ls -d $pdir/$2/*/ | sed s:$pdir/$2/:: | column ;;
    C) ls -d $pdir/*/ | sed s:$pdir/:: | column ;;
    n) more $pdir/*/$2/pkg-descr ;;
    s) find $pdir/* -maxdepth 1 -iname *$2* | sed s:$pdir/:: | sort -d | column ;;
    d) make -C $pdir/*/$2 all-depends-list | sed s:$pdir/:: | sort -d | column ;;
    b) make -C $pdir/*/$2 build-depends-list | sed s:$pdir/:: | sort -d | column ;;
    m) make -C $pdir/*/$2 missing | sed s:$pdir/:: | sort -d | column ;;
    r) make -C $pdir/*/$2 run-depends-list | sed s:$pdir/:: | sort -d | column ;;
   \?) echo "Type "pd.sh -h" for help." >&2 ;;
      esac
done

if [ -z "$1" ]
then
    echo "usage: pd.sh [-hcCnsdbmr] port-name...
Type "pd.sh -h" for help"
fi
 
It's common to not hard code the name of the script into itself but use the $0 variable in place of it:

Code:
if [ -z "$1" ]
then
    echo "usage: $0 [-hcCnsdbmr] port-name...
Type "$0 -h" for help"
fi
 
Back
Top