ソフトな忘却力(1) リモート接続でLinux Cプログラミング getopt

Joseph Halfmoon

なんだかな~。老人の忘却力とて、使わなければ忘れるし、使わないものはもともと覚えていないし(当然か。)このところマイコンの「クロス」開発ばかりで、ホスト上の「セルフ」がないです。普通にCとかPythonとかのプログラミングも忘れてしまいそうなので、忘却いや記憶の鍛錬を。今回はコマンドライン引数の処理、C言語編。

(実習に使用したソースは末尾に)

コマンドライン引数の処理、Pythonであると argparse に頼り切っております。しかし、Cは「クロス」ばかり。コマンドライン引数など滅多に扱いませぬ。たまに必要に迫られても argc, argvで力業で足りている。

それではいけないとライブラリを呼び出してみることにいたします。いろいろ近代的で良いものが多数あり~の、でありますが、ここは定番、最初は getopt でありましょう。Web上のmanページ(日本語)へのリンクは以下に。

Man page of GETOPT

末尾の実習コードの内容

コマンドラインから、2個のフラグ(-a, -b)と、1個の数値パラメータ(-C)を受け取って、設定状況を表示するだけのプログラムです。実体処理は何もなし。コマンドライン記述のエラーについては getopt の機能で、ほぼほぼお任せ。

実習の方針

実習をするにあたっては、以下の縛りを掲げました。

  1. PC上のVS CodeからRaspberry Pi OSで動作しているRaspberry Pi 3にリモート接続して、Pi 3上で「セルフ」開発する。
  2. VS Codeの”EXTENSIONS” の「ご支援」をステップ毎に確認しながらやる

今回使用する VS Code の ”EXTENSIONS” は以下の2つです。

  • Microsoft C/C++
  • Microsoft C/C++ Extension Pack
ビルド

とりあえず「開く」対象の空のフォルダのみ作成した状態でVS Codeからリモート接続してフォルダを開きました。そして末尾に掲げたソース(1ファイルのみ)を記述いたしました(勿論、最初はエラーあり~の)

末尾のようなシンプルな1ファイルであれば、とくに Makefileなどなにも記述しないで、コマンドパレットからビルドを指定してもビルドは進行します。ただし、どのツールチェーンを使うのかは選択肢が表示されるので指定が必要です。今回は、ラズパイのコマンドラインから起動するプログラムを作るので、「クロス開発用でないLinux用の普通の」/usr/bin/gcc を指定しました。初回ビルド(コンパイルエラーで落ちた)後見てみると、自動でサブディレクトリ .vscode が作られ、その中に settings.json も作成されてました。

settings.json VS Codeの各種設定を記録するためのファイル

だと思います。エラーを直して、再ビルド完了後、VS Codeからリモートのシェル(bash)を開けば実行が可能です。こんな感じ。エラーのケースもやってみましたけれど、一応、期待どおり。

$ ./getoptTST -a -b -C 12
flagA : 1
flagB : 1
paramC : 12

$ ./getoptTST -a -b -x 12
./getoptTST: invalid option -- 'x'
Usage: ./getoptTST [-a][-b][-C] nnn

なお、ビルドがキッカケなのだと思いますが 自動で tasks.json (ビルドなどの設定を保存しているファイル)も生成されていました。

tasks.json 、このファイル内でコンパイル・オプションなどの記述ができます。今回は何も指定していなのですが、-g(デバッグ情報を含める)になっていました。他の最適化オプションなどは何もなかったので、必要であればこのファイルを修正すれば良いのだと思います。

自動生成された tasks.json

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: gcc アクティブなファイルのビルド",
            "command": "/usr/bin/gcc",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "デバッガーによって生成されたタスク。"
        }
    ],
    "version": "2.0.0"
}
デバッグ

とりあえず、何も設定せずにVS Codeのメニューから Start Debugging してみると、「ブレークせずに実行完了してしまう」です。まだ、デバッガで起動するときのコマンドライン引数などは何も指定なしの状態です。

ただ1回目の実行により launch.json (デバッグ時の設定などを記録するためのファイル)が自動生成されていました。このファイルの中に、デバッグ時のプログラム引数として与えるための指定、args も存在します。自動生成ファイルでは空であったので、以下の例のようにargsのところを手動で書き換えました。

生成された launch.json に手動でコマンドライン引数を加えたもの

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "gcc - アクティブ ファイルのビルドとデバッグ",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoExtension}",
            "args": ["-a", "-b", "-C 12"],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: gcc アクティブなファイルのビルド",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}

launch.jsonの手動修正後、さらにソースコード上にブレークポイントを設定してから Start Debugging してみます。その様子は先頭のアイキャッチ画像に。ブレークポイントで止まり、ローカル変数もウインドウに表示されており、ステップ実行などのためのボタンも上部でアクティブになっています。

VS CodeとそのExtensionsのお陰で、例によってドキュメントも読まず、テキトーにやっていても、とりあえずgetopt 動いた?みたい。覚えたことは忘れないでね、自分。

練習に使ったCのソースコード
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    int flagA = 0;
    int flagB = 0;
    int paramC = 0;
    int opt;
    int temp;

    while((opt = getopt(argc, argv, "abC:")) != -1) {
        switch (opt) {
            case 'a':
                flagA = 1;
                break;
            case 'b':
                flagB = 1;
                break;
            case 'C':
                errno = 0;
                temp = strtol(optarg, (char **)NULL, 10);
                if (errno == 0) {
                    paramC = temp;
                } else {
                    perror("paramC");
                }
                break;
            default:
                fprintf(stderr, "Usage: %s [-a][-b][-C] nnn\n", argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    printf("flagA : %d\n", flagA);
    printf("flagB : %d\n", flagB);
    printf("paramC : %d\n", paramC);
}

ソフトな忘却力(2) ラズパイ4でCプログラミング、libconfig へ進む