challenge 起動オプションの解析

いわゆる、コマンドライン引数の取得(http://ja.doukaku.org/118/)からの派生です。
やっぱ、自分のコマンドってオプションつけたいですよね(笑
タグに「クックブック」なんてつけてみました
長文なのはご容赦ください^^;;
-----
次の起動インタフェースを持つコマンドを作成してください。

書式:cmdopt -o [-q] -d{0|1|2} 文字列 [文字列 ...]

書式を説明すると
- オプション「o」
  必須オプションです。指定されていない場合、異常終了してください。
- オプション「q」
  選択オプションです。
  省略されていても問題有りません。
- オプション「d」
  引数付きオプションです。
  「0」「1」「2」のいずかが続いて指定されます。
- 文字列
  パラメータです。
  1つ以上であればいくつでも指定できます。
  指定されていなかった場合、異常終了してください。

オプションの開始が「-」になっていますが
「+」や「/」でもかまいません。
余力があればロングオプションに対応してもよいです。

起動例:(すべて許容されるのが望ましいです)
1. cmdopt -o AAA
2. cmdopt -o AAA BBB CCC
3. cmdopt -oq AAA
4. cmdopt -o  -q AAA
5. cmdopt -o -s1 AAA
6. cmdopt -o -s 1 AAA
7. cmdopt -q -s2 -o AAA

出力例:
[オプション情報]
o(output): ON|OFF
q(quote): ON|OFF
d(debug): 0|1|2 

[パラメータ情報]
指定数: N
1: 文字列1
2: 文字列2
...
N: 文字列N
えーと。何人かの方から指摘されていますが
-sは-dの間違いです。
また、-dのオプションは省略可能なオプションでした。

ということで書式を訂正すると
書式:cmdopt -o [-q] [-d{0|1|2}] 文字列 [文字列 ...]
です。

それと起動例も
1. cmdopt -o AAA
2. cmdopt -o AAA BBB CCC
3. cmdopt -oq AAA
4. cmdopt -o  -q AAA
5. cmdopt -o -d1 AAA
6. cmdopt -o -d 1 AAA
7. cmdopt -q -d2 -o AAA
となります。

フォロー遅くなってしまって申し訳ないです。
期末恒例のお祭りが発生しちゃって^^;;
指摘ありがとうございました。

Posted feedbacks - Nested

Flatten Hidden
ちょうど、今日TwitterでBoost.Program_optionsの話題が出てたとこでした。
なんというタイミング。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <vector>
#include <string>
#include <exception>

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>

int main(int c, char**v)
{
  try {
    namespace po = boost::program_options;

    int debug;

    po::options_description desc("Options");
    desc.add_options()
      ("output,o", "output flag [required]")
      ("quote,q", "quote flag")
      ("debug,d", po::value< int >(&debug)->default_value(0), "debug level [0|1|2]")
      ;
    po::options_description hidden("hidden optionis");
    hidden.add_options()
      ("input", po::value< std::vector< std::string > >(), "input file [at least one required]")
      ;

    po::positional_options_description positional;
    positional.add("input", -1);

    po::options_description visible("Allowed Options");
    visible.add(desc);

    po::options_description full("Full Options");
    full.add(desc).add(hidden);

    po::variables_map vm;
    po::store(po::command_line_parser(c, v).options(full).positional(positional).run(), vm);
    po::notify(vm);    

    bool error=false;
    if ( !vm.count("output") ) {
      std::cerr << "*** you must specify -o option\n\n";
      error = true;
    }
    if ( !vm.count("input") ) {
      std::cerr << "*** you must specify at least 1 input parameter\n\n";
      error = true;
    }
    if ( debug < 0 || debug > 2 ) {
      std::cerr << "*** debug level must be 0, 1 or 2\n\n";
      error = true;
    }
    
    if ( error ) {
      std::cerr << visible << "\n";
      return 1;
    }

    bool output = vm.count("output");
    bool quote  = vm.count("quote");

    if ( output ) {
      std::cout << "[オプション情報]\n";
      std::cout << "o(output) : " << (output ? "ON" : "OFF") << "\n";
      std::cout << "q(quote)  : " << (quote  ? "ON" : "OFF") << "\n";
      std::cout << "d(debug)  : " << debug << "\n\n";

      std::cout << "[パラメタ情報]\n";
      const std::vector<std::string>& input = vm["input"].as< std::vector<std::string> >();
      std::cout << "指定数 : " << input.size() << "\n";
      for ( int i = 0; i < input.size(); ++i )
        std::cout << i+1 << " : " << (quote?"\"":"") << input[i] << (quote?"\"":"") << "\n";
    }

    return 0;
  } catch (const std::exception& e) {
    std::cerr << e.what() << "\n";
    return -1;
  }
}
許容例の -s は、-d の間違いだと思われる。 -d は、省略できないオプションのように思われる。
-d は、必須オプションとして処理しています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use Getopt::Std;

#ex>cmdopt -o [-q] -d{0|1|2} 文字列 [文字列 ...]

our( $opt_o, $opt_q, $opt_d );

getopts('oqd:');

die "oオプションが指定されていない\n" unless $opt_o;
die "dオプションが指定されていない\n" unless defined $opt_d;
die "dオプションの引数は0-2\n" unless ($opt_d eq '0' || $opt_d eq '1' || $opt_d eq '2');
die "パラメータが指定されていない\n" unless @ARGV;

print "[オプション情報]\n";
print "o(output): ON\n" if $opt_o; 
print "q(quote): " . ($opt_q ? "ON" : "OFF") . "\n"; 
print "d(debug): $opt_d\n";

print "\n[パラメータ情報]\n";
printf("指定数: %d\n", $#ARGV+1);

my $c = 0;
for my $arg (@ARGV){
    $c++;
    print "$c: $arg\n";
}
いつも使ってる解析ルーチンをベースにしてみました。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>

struct cmd_opt{
    int opt_o;
    int opt_q;
    int opt_d;
    int string_start;
    int string_num;
};


int cmdline(int argc,char* argv[],struct cmd_opt*o){
    char* p;
    int i=1;
    int opt_flag=0;
    
    //必ず-oと文字列が必要
    if(argc<3) return -1;
    
    o->opt_o=0;
    o->opt_q=0;
    o->opt_d=-1;
    o->string_start=0;
    o->string_num=0;
    
    p=argv[1];
    
    while(i<=argc){
        switch(*p){
        case '-':
        case '+':
        case '/':
            p++;
            opt_flag=1;
            break;
        default:
            break;
        }
        
        //オプション解析終了
        if(opt_flag==0){
            if(o->opt_o==0) return -1;
            if(o->opt_d==-1) return -1;
            o->string_start=i;
            o->string_num=argc-i;
            if(o->string_num==0) return -1;
            else return 0;
        }
        
        switch(*p){
        case 'o':
        case 'O':
            o->opt_o=1;
            break;

        case 'q':
        case 'Q':
            o->opt_q=1;
            break;

        case 'd':
        case 'D':
            p++;;
            if(*p==0){
                p=argv[++i];
            }
            if('0'>*p||*p>'2')
                return -1;
            o->opt_d=*p-'0';
            break;
        default:
            return -1;
            break;
        }
        p++;
        if(*p==0){
            p=argv[++i];
            opt_flag=0;
        }
    }
    return -1;
}

void put_usage(){
    printf("usage:cmdopt -o [-q] -d{0|1|2} 文字列 [文字列 ...]\n");
}

int main(int argc,char* argv[]){
    int ret;
    int i;
    
    struct cmd_opt o;
    
    ret=cmdline(argc,argv,&o);
    
    if(ret==-1){
        put_usage();
        return -1;
    }
    
    printf("[Option Info]\n");
    printf("o(output):%s\n",o.opt_o?"ON":"OFF");
    printf("q(output):%s\n",o.opt_q?"ON":"OFF");
    printf("d(output):%d\n",o.opt_d);
    
    printf("\n");
    printf("[Parameter Info]\n");
    printf("String Num:%d\n",o.string_num);
    for(i=1;i<=o.string_num;i++){
        printf("%d:%s\n",i,argv[i+o.string_start-1]);
    }
    return 0;
}
バッチファイル。遅延環境変数展開で。
お題の起動例にあるパターンはすべて解釈できていると思います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@echo off
setlocal enabledelayedexpansion

set OPT_O=OFF
set OPT_Q=OFF
set ARGV=
set WAIT_OPT_S=0
shift

for %%1 in (%*) do (
  call :parse_arg %%1
)
goto parse_end

rem *** パラメータ解析 ***
:parse_arg
set PARA=%1
if "%PARA:~0,1%"=="-" (
  set WAIT_OPT_S=0
  call :parse_opt %PARA:~1%
) else (
  if "%WAIT_OPT_S%"=="1" set OPT_S=%PARA% & set WAIT_OPT_S=0 & goto :EOF
  set ARGV=%ARGV% %PARA%
)
goto :EOF

rem *** オプション解析 ***
:parse_opt
set OPT=%1
:parse_an_opt
if "%OPT%"=="" goto :EOF
set OPT2=%OPT:~0,1%
if "%OPT2%"=="o" (
  set OPT_O=ON
) else if "%OPT2%"=="q" (
  set OPT_Q=ON
) else if "%OPT2%"=="s" (
  set OPT_S=%OPT:~1,1%
  set OPT=%OPT:~1%
  if "!OPT_S!"=="" set WAIT_OPT_S=1
)
set OPT=%OPT:~1%
goto :parse_an_opt

rem *** 結果出力 ***
:parse_end
echo [オプション情報]
echo o : %OPT_O%
echo q : %OPT_Q%
echo s : %OPT_S%

set /A COUNT=0
for %%1 in (%ARGV%) do set /A COUNT += 1

set /A ARGC=1
echo [パラメータ情報]
echo 指定数:%COUNT%
for %%1 in (%ARGV%) do (
  echo !ARGC! : %%1
  set /A ARGC += 1
)

endlocal
おっと、異常終了とか忘れてた。
バッチファイルの異常終了ってこんなんでいいのかな。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
46a47,53
>
> set /A COUNT=0
> for %%1 in (%ARGV%) do set /A COUNT += 1
>
> if not "%OPT_O%"=="ON" echo oオプションは必須です。 & exit /b 1
> if "%COUNT%"=="0"  echo パラメータは必須です。 & exit /b 1
>
52,54d58
< set /A COUNT=0
< for %%1 in (%ARGV%) do set /A COUNT += 1
<
63a68
> exit /b 0

pure bashで内蔵コマンドgetoptsを使いました。

起動例の-sはコマンドじゃなくて引数扱い、と解釈したために、その部分がBKっぽくなっています。

あと、-oは必須ということで、そこの出力は手抜きしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/bin/bash
OPTERR=0

args=()
nargs=0

opt_o=OFF
opt_q=OFF
opt_d=''

while [ $# != 0 ];do
    oprind_orig=$OPTIND
    while getopts 'oqd:*' opt;do
        case "$opt" in
        o)  opt_o=ON
            ;;
        q)  opt_q=ON
            ;;
        d)  opt_d=$OPTARG
            [[ $opt_d == [012] ]] || exit 1
            ;;
        \?) [[ ! ${!OPTIND} == -* ]] && ((OPTIND--))
            break
            ;;
        esac
    done
    shift $((OPTIND - 1))
    OPTIND=1
    [ $# != 0 ] && args[$((nargs++))]=$1
    shift
done

[ "$opt_o" = OFF ] && exit 1
((nargs)) || exit 1

echo "[オプション情報]
o(output): ON
q(quote):  $opt_q
d(debug):  $opt_d

[パラメータ情報]
指定数: $nargs"

for ((i = 0; i < nargs; i++));do
    echo "$((i + 1)): ${args[$i]}"
done

#7614の訂正にしたがって簡略化してみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
while getopts 'oqd:*' opt;do
    case "$opt" in
    o)  opt_o=ON ;;
    q)  opt_q=ON ;;
    d)  opt_d=$OPTARG
        [[ $opt_d == [012] ]] || exit 1
        ;;
    esac
done
shift $((OPTIND - 1))

[ -z "$opt_o" ] && exit 1
[ $# = 0 ] && exit 1

echo "[オプション情報]
o(output): ON
q(quote):  ${opt_q:-OFF}
d(debug):  ${opt_d:-}

[パラメータ情報]
指定数: $#"

i=0
for e in "$@";do
    echo "$((++i)): $e"
    shift
done

Squeak Smalltalk で、http://ja.doukaku.org/comment/5013/ の方法で得た引数の配列を解析するものを書いてみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| cmdopt |

cmdopt := [:args |
    | output quote debug strings argsStream |
    output := false.
    quote := false.
    debug := 0.
    strings := OrderedCollection new.
    argsStream := args readStream.
    [argsStream atEnd] whileFalse: [
        | opt |
        opt := argsStream next.
        (opt first = $-) ifFalse: [strings add: opt] ifTrue: [
            | optStream nextOpt |
            optStream := opt allButFirst readStream.
            [(nextOpt := optStream next) notNil] whileTrue: [
                nextOpt caseOf: {
                    [$o] -> [output := true].
                    [$q] -> [quote := true].
                    [$d] -> [debug := Integer readFrom: (optStream atEnd
                        ifFalse: [optStream] ifTrue: [argsStream next readStream])]
                } otherwise: [^self error: 'unknown option']]]].
    output ifFalse: [^self error: 'no output option'].
    strings ifEmpty: [^self error: 'no string option'].
    {#output->output. #quote->quote. #debug->debug. #strings->strings asArray}].

World findATranscript: nil.
#(
    ('-o' 'AAA')
    ('-o' 'AAA' 'BBB' 'CCC')
    ('-oq' 'AAA')
    ('-o' '-q' 'AAA')
    ('-o' '-d1' 'AAA')
    ('-o' '-d' '1' 'AAA')
    ('-q' '-d2' '-o' 'AAA')
) do: [:args |
    Transcript cr; show: (cmdopt value: args)]

"=>
    {#output->true . #quote->false . #debug->0 . #strings->#('AAA')}
    {#output->true . #quote->false . #debug->0 . #strings->#('AAA' 'BBB' 'CCC')}
    {#output->true . #quote->true . #debug->0 . #strings->#('AAA')}
    {#output->true . #quote->true . #debug->0 . #strings->#('AAA')}
    {#output->true . #quote->false . #debug->1 . #strings->#('AAA')}
    {#output->true . #quote->false . #debug->1 . #strings->#('AAA')}
    {#output->true . #quote->true . #debug->2 . #strings->#('AAA')}
"

optparse

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python
# -*- coding: cp932 -*-

from optparse import OptionParser
def main():
    usage = u'usage: cmdopt -o [-q] -d{0|1|2} 文字列 [文字列 ...]'
    parser = OptionParser(usage)
    parser.add_option('-o', '--output', action='store_true')
    parser.add_option('-q', '--quote', action='store_true')
    parser.add_option('-d', '--debug', type='int',default=0)
    (option, args) = parser.parse_args()
    if not option.output: parser.error('output option error')
    if option.debug not in [0,1,2]: parser.error('debug option error')
    if not len(args): parser.error('parameter error')

    print u'[オプション情報]'
    print u'o(output): ON'
    print u'q(quote): %s' % ('ON' if option.quote else 'OFF')
    print u'd(debug): %d' % option.debug
    print u'\n[パラメーター情報]'
    print u'指定数: %d' % len(args)
    for i, param in enumerate(args):
        print u'%d: %s' % (i+1, param)

if __name__ == "__main__":
    main()

SRFI-37 や SLIB を使う方法もありますが、とりあえずは gauche.parseopt 版。 -oq のような指定や -d1, -d2 のような指定には対応していないようです。

ところで起動例にある -s オプションは -d ですよね?

;; エラー処理で悩んでしまった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(use gauche.parseopt)
(use gauche.sequence)

(define (main args)
  (let-args (cdr args)
      ((output "o|output" #f)
       (quiet  "q|quiet"  #f)
       (debug  "d|debug=i" 0)
       . argv)
    (cond
     ((not output)
      (format (current-error-port)
              "output option is not supplied.~%"))
     ((not (memv debug '(0 1 2)))
      (format (current-error-port)
              "debug option's value must be 0, 1, or 2: ~A~%" debug))
     ((null? argv)
      (format (current-error-port)
              "no arguments are supplied.~%"))
     (else
      (format #t "[options]~%o(output): ON~%q(quote): ~A~%d(debug): ~A~%~%"
              (if quiet 'ON 'OFF)
              debug)
      (format #t "[parameters]~%# of pamameters: ~A~%" (length argv))
      (for-each-with-index (lambda (i x) (format #t "~A: ~A~%" (+ i 1) x))
                           argv)
      (exit 0)))
    1))

args-fold 版。ロングオプション対応。起動例のすべてのパターンを受け付けます(-s を -d にした場合)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
(use srfi-37)
(use gauche.sequence)

(define (main args)
  (receive (debug output quiet rargv)
      (args-fold (cdr args)
                 (list
                  (option '(#\d "debug") #t #f
                          (lambda (option name arg debug output quiet argv)
                            (values (string->number arg) output quiet argv)))
                  (option '(#\o "output") #f #f
                          (lambda (option name arg debug output quiet argv)
                            (values debug #t quiet argv)))
                  (option '(#\q "quiet") #f #f
                          (lambda (option name arg debug output quiet argv)
                            (values debug output #t argv)))
                  )
                 (lambda (option name arg . _)
                   (error "Unrecognized option: " name))
                 (lambda (op debug output quiet argv)
                   (values debug output quiet (cons op argv)))
                 0 #f #f '())
    (cond
     ((not output)
      (format (current-error-port)
              "output option is not supplied.~%"))
     ((not (memv debug '(0 1 2)))
      (format (current-error-port)
              "debug option's value must be 0, 1, or 2: ~A~%" debug))
     ((null? rargv)
      (format (current-error-port)
              "no arguments are supplied.~%"))
     (else
      (format #t "[options]~%o(output): ON~%q(quiet): ~A~%d(debug): ~A~%~%"
              (if quiet 'ON 'OFF)
              debug)
      (format #t "[parameters]~%# of pamameters: ~A~%" (length rargv))
      (for-each-with-index (lambda (i x) (format #t "~A: ~A~%" (+ i 1) x))
                           (reverse rargv))
      (exit 0)))
    1))
少々無理矢理な感じです。。。Parsecが使えたらもっと楽なのでしょうね(^^;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
data Args = Args {
    outputFlag :: Bool,
    quoteFlag :: Bool,
    debugLevel :: Int,
    names :: [String]
} deriving (Show)

newArgs =   Args {
        outputFlag = False,
        quoteFlag = False,
        debugLevel = 0,
        names = []
    }

parse [] result = result

parse (arg:args) result
    | arg == "-d"       = parse (tail args) result{ debugLevel = read $ head args }
    | opt == "-d"       = parse args result{ debugLevel = read num }
    | (head arg) == '-' = parse args $ parse' (tail arg) result
    | otherwise         = parse args result{ names = (names result) ++ [arg] }
    where
        (opt,num) = splitAt 2 arg
        parse' [] res = res
        parse' ('o':xs) res = parse' xs res{ outputFlag = True }
        parse' ('q':xs) res = parse' xs res{ quoteFlag = True }
        parse' _ _ =    error $ arg ++ " is unknown option."

optParse :: [String] -> Args
optParse args
    | (outputFlag result) == False  =   error "-o is need option."
    | (names result) == []  =   error "need file name."
    | otherwise     =   result
    where
        result = parse args newArgs

dump args = do
    putStrLn "[Option information]"
    putStrLn $ "o(output) : " ++ (onoff $ outputFlag args)
    putStrLn $ "q(quote) : " ++ (onoff $ quoteFlag args)
    putStrLn $ "d(debug) : " ++ (show $ debugLevel args)
    putStrLn ""
    putStrLn "[Paramater information]"
    putStrLn $ "count : " ++ (show $ length $ names args)
    mapM (\(no,name)->putStrLn $ (show no) ++ " : " ++ name) $ zip [1,2..] $ names args
    putStrLn ""
    where
        onoff True = "ON"
        onoff _ = "OFF"

main = do
    args <- getArgs
    dump $ optParse args
{--
    mapM_ test testData
    mapM_ dumps testData
    where
        dumps arg = dump $ optParse arg
        test arg = putStrLn $ show $ optParse arg
        testData = map words ["-o AAA",
                    "-o AAA BBB CCC",
                    "-oq AAA",
                    "-o -q AAA",
                    "-o -d1 AAA",
                    "-o -d 1 AAA",
                    "-q -d2 -o AAA"]
--}

System.Console.GetOpt を使う方法もあります。 ロングオプションにも対応しています。

でもあんまりすっきりとはいかない感じです。:<

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
module Main where

import System.Console.GetOpt
import System.Environment

main :: IO ()
main = getArgs >>= compilerOpts >>= cmdopt

data Options = Options 
    { optOutput :: Bool
    , optQuote  :: Bool
    , optDebug  :: Int
    }

defaultOptions = Options
    { optOutput = False
    , optQuote  = False
    , optDebug  = 0
    }

options :: [OptDescr (Options -> Options)]
options =
 [ Option ['o'] ["output"]
   (NoArg (\ opts -> opts { optOutput = True }))
   "Output option"
 , Option ['q'] ["quote"]
   (NoArg (\ opts -> opts { optQuote  = True }))
   "Quote option"
 , Option ['d'] ["debug"]
   (ReqArg (\ d opts -> opts { optDebug = read d }) "LEVEL")
   "debug LEVEL"
 ]

compilerOpts :: [String] -> IO (Options, [String])
compilerOpts argv
 = case getOpt Permute options argv of
     (o,n,[])  -> return (foldl (flip id) defaultOptions o, n)
     (_,_,ers) -> ioError (userError (concat ers ++ usageInfo usageHeader options))

usageHeader = "Usage: cmdopt -o [-q] [-d {0|1|2}] STR [STR ...]"

cmdopt :: (Options,[String]) -> IO ()
cmdopt (o,xs@(_:_))
 | optOutput o = putStr $ unlines 
               $ ["[Option Info]"
                 ,"o(output): "++"ON"
                 ,"q(quote) : "++if optQuote o then "ON" else "OFF"
                 ,"d(debug) : "++show (optDebug o)
                 ,"[Parameter Info]"
                 ,show len ++ " parameter"++if len >1 then "s " else " " ++"specified"
                 ] 
               ++ map showParam (zip [1..] xs)
    where len = length xs
cmdopt _       = ioError (userError (usageInfo usageHeader options))

showParam :: (Int,String) -> String
showParam (n,s) = show n ++": "++s
とりあえず getopt()を利用したものです。
コンパイラによってはgetopt()が unistd.hにはいっているかもしれません。

僕はいつも起動情報を保持する構造体を作って保持しています。
has~()という関数作ってしまえばあんまり関係ないですけど。。
// gcc -Wall -std=c99 doukaku205.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
//#include <unistd.h>
#include <getopt.h>

#define OPTION_OUTPUT 'o'
#define OPTION_QUOTE  'q'
#define OPTION_DEBUG  'd'

static char options[] = { ':', 
                     OPTION_OUTPUT
                     , OPTION_QUOTE
                     , OPTION_DEBUG, ':'
                    , '\0' };

struct tagParamInfo
{
    bool output;
    bool quote;
    int  debug;
    int  num;
    char *argv0;
    char **argv;
} info = { false, false, 0, 0, NULL, NULL};

int main(int argc, char *argv[])
{
    int opt;

    /* デフォル値 設定 */

    /* オプション解析 */
    opterr = 0;
    while( (opt = getopt(argc, argv, options)) != EOF )
    {
        switch( opt )
        {
            case OPTION_OUTPUT:
                info.output = true;
                break;
            case OPTION_QUOTE:
                info.quote = true;
                break;
            case OPTION_DEBUG:
                info.debug = strtol(optarg, NULL,10);
                if( 0 <= info.debug && info.debug <= 2 )
                {
                    break;
                }
            case '?':
            case ':':
            default:
                printf("変なオプション[%c]\n", opt);
                printf("書式:cmdopt -o [-q] [-d{0|1|2}] 文字列 [文字列 ...]\n");
                return 1;
        }
    }

    /* パラメータ設定 */
    info.argv0 = argv[0];
    info.argv  = &argv[optind];
    info.num = argc - optind;
    if( info.num < 1 )
    {
        printf("文字列がない\n");
        printf("書式:cmdopt -o [-q] [-d{0|1|2}] 文字列 [文字列 ...]\n");
        return 1;
    }

    /* 解析結果の出力 */
    printf("[オプション情報]\n");
    printf("%-10s:%3s\n", "o(output)", ((info.output==true)?"ON":"OFF"));
    printf("%-10s:%3s\n", "q(quote)", ((info.quote==true)?"ON":"OFF"));
    printf("%-10s:%3d\n", "d(debug)", info.debug);
    printf("\n");
    printf("[パラメータ情報]\n");
    printf("%-10s:%3d\n", "指定数", info.num);
    for( int n=0; n<info.num; n++)
    {
        printf("%2d:%s\n", n, info.argv[n]);
    }
    return 0;
}
sh版作っていて-oオプションのチェックが抜けていたことに気づくorz
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
--- doukaku205.c        2008-09-10 22:23:39.953125000 +0900
+++ doukaku205.c.new    2008-09-10 22:43:58.656250000 +0900
@@ -61,6 +61,14 @@
     info.argv0 = argv[0];
     info.argv  = &argv[optind];
     info.num = argc - optind;
+
+    /* 必須チェック */
+    if( info.output != true )
+    {
+        printf("必須オプションがたりない\n");
+        printf("書式:cmdopt -o [-q] [-d{0|1|2}] 文字列 [文字列 ...]\n");
+        return 1;
+    }
     if( info.num < 1 )
     {
         printf("文字列がない\n");