第12章 プログラミング

# 第12章 プログラミング

Debian システム上でプログラミングを学ぶ人がパッケージ化されたソースコードを読み込めるようになるための指針を示します。以下はプログラムに関する特記すべきパッケージと対応する文書パッケージです。

オンラインリファレンスは `manpages` と `manpages-dev` パッケージをインストールした後で "`man name`" とタイプすると利用可能です。GNU ツールのオンラインリファレンスは該当する文書パッケージをインストールした後で "`info program_name`" とタイプすると使えます。一部の GFDL 文書は DFSG に準拠していないと考えられているので `main` アーカイブに加えて `contrib` や `non-free` アーカイブを含める必要があるかもしれません。

バージョン コントロール システム ツールの使用を考えましょう。「Git」を参照下さい。

| 警告
"`test`" を実行可能なテストファイルの名前に用いてはいけません。"`test`" はシェルのビルトインです。

| 注意
ソースから直接コンパイルしたソフトウェアープログラムは、システムプログラムとかち合わないように、"`/usr/local`" か "`/opt`" の中にインストールします。

| ヒント
"99ボトルのビールの歌" 作成のコード例はほとんど全てのプログラム言語に関する理解のための非常に好適です。

## 12.1. シェルスクリプト

シェルスクリプトは実行ビットがセットされたテキストファイルで、以下に示すフォーマットのコマンドを含んでいます。

    #!/bin/sh
     ... command lines

最初の行はこのファイル内容を読み実行するシェルインタープリタを指定します。

シェルスクリプトを読むのは Unix 的なシステムがどのように機能しているのかを理解する最良の方法です。ここでは、シェルプログラムに関する指針や心がけを記します。失敗から学ぶために "シェルの失敗" (https://www.greenend.org.uk/rjk/2001/04/shell.html) を参照下さい。

シェル対話モード (「シェルプロンプト」と「Unix 的テキスト処理」を参照下さい) と異なり、シェルスクリプトは変数や条件文やループを繁用します。

### 12.1.1. POSIX シェル互換性

多くのシステムスクリプトは POSIX シェル (表1.13「シェルプログラムのリスト」を参照下さい) のどれで解釈されるか分かりません。

- デフォールトの非インタラクティブなシェル "`/usr/bin/sh`" は `/usr/bin/dash` をさしているシムリンクで、多くのシステムプログラムで使われます。

- デフォルトのインタラクティブなシェルは `/usr/bin/bash` です。

全ての POSIX シェル間でポータブルとするために bashisms や zshisms を使うシェルスクリプトを書くのを避けます。checkbashisms(1) を使うとこれがチェックできます。

表12.1 典型的 bashizms のリスト

推薦: POSIX | 回避すべき: bashism
--- | ---
`if [ "$foo" = "$bar" ] ; then …` | `if [ "$foo" == "$bar" ] ; then …`
`diff -u file.c.orig file.c` | `diff -u file.c{.orig,}`
`mkdir /foobar /foobaz` | `mkdir /foo{bar,baz}`
`funcname() { … }` | `function funcname() { … }`
8進表記: "`\377`" | 16進表記: "`\xff`"

"`echo`" コマンドはその実装がシェルビルトインや外部コマンド間で相違しているので以下の注意点を守って使わなければいけません。

- "`-n`" 以外のどのコマンドオプション使用も避けます。

- 文字列中にエスケープシーケンスはその取扱いに相違があるので使用を避けます。

| 注記
"`-n`" オプションは実は POSIX シンタックスではありませんが、一般的に許容されています。

| ヒント
出力文字列にエスケープシーケンスを埋め込む必要がある場合には、"`echo`" コマンドの代わりに "`printf`" コマンドを使います。

### 12.1.2. シェル変数

特別なシェルパラメーターがシェルスクリプト中ではよく使われます。

表12.2 シェル変数のリスト

シェル変数 | 変数値
--- | ---
`$0` | シェルまたはシェルスクリプトの名前
`$1` | 最初 (1番目) のシェル引数
`$9` | 9番目のシェル引数
`$#` | シェル引数の数
`"$*"` | `"$1 $2 $3 $4 … "`
`"$@"` | `"$1" "$2" "$3" "$4" …`
`$?` | 最新のコマンドの終了状態
`$$` | このシェルスクリプトの PID
`$!` | 最近スタートしたバックグラウンドジョブの PID

覚えておくべき基本的なパラメーター展開を次に記します。

表12.3 シェル変数展開のリスト

パラメーター式形 | `var` が設定されているときの値 | `var` が設定されていないときの値
--- | --- | ---
`${var:-string}` | "`$var`" | "`string`"
`${var:+string}` | "`string`" | "`null`"
`${var:=string}` | "`$var`" | "`string`" (合わせて "`var=string`" を実行)
`${var:?string}` | "`$var`" | "`string`" をstderr に出力 (エラーとともに exit する)

ここで、これら全てのオペレーターのコロン "`:`" は実際はオプションです。

- "`:`" 付き = 演算子は存在と非ヌル文字列をテストします

- "`:`" 無し = 演算子は存在のみをテストします

表12.4 重要なシェル変数置換のリスト

パラメーター置換形 | 結果
--- | ---
`${var%suffix}` | 最短のサフィックスパターンを削除
`${var%%suffix}` | 最長のサフィックスパターンを削除
`${var#prefix}` | 最短のプレフィックスパターンを削除
`${var##prefix}` | 最長のプレフィックスパターンを削除

### 12.1.3. シェル条件式

各コマンドは条件式に使えるエグジットステイタスを返します。

- 成功: 0 ("真")

- エラー: 非0 ("偽")

| 注記
シェル条件文の文脈において "0" は "真" を意味しますが、C 条件文の文脈では "0" は "偽" を意味します。

| 注記
"`[`" は、"`]`" までの引数を条件式として評価する、`test` コマンドと等価です。

覚えておくべき基本的な条件文の慣用句は次です。

- "`command && 成功したらこのcommandも実行 || true`"

- "`command || もしcommandが成功しないとこのコマンドも実行 || true`"

- 以下のような複数行のスクリプト断片

    if [ conditional_expression ]; then
     if_success_run_this_command
    else
     if_not_success_run_this_command
    fi

ここで、シェルが "`-e`" フラグ付きで起動された際にシェルスクリプトがこの行で誤って exit しないようにするために、末尾の "`|| true`" が必要です。

表12.5 条件式中のファイル比較演算子

式 | 論理真を返す条件
--- | ---
`-e file` | file が存在する
`-d file` | file が存在しディレクトリーである
`-f file` | file が存在し通常ファイルである
`-w file` | file が存在し書込み可能である
`-x file` | file が存在し実行可能である
`file1 -nt file2` | file1 が file2 よりも新しい (変更)
`file1 -ot file2` | file1 が file2 よりも古い (変更)
`file1 -ef file2` | file1 と file2 は同デバイス上の同 inode 番号

表12.6 条件式中での文字列比較演算子のリスト

式 | 論理真を返す条件
--- | ---
`-z str` | str の長さがゼロ
`-n str` | str の長さが非ゼロ
`str1 = str2` | str1 と str2 は等しい
`str1 != str2` | str1 と str2 は等しく無い
`str1 < str2` | str1 のソート順が str2 より前 (ロケール依存)
`str1 > str2` | str1 のソート順が str2 より後 (ロケール依存)

条件式中の算術整数比較演算子は "`-eq`" と "`-ne`" と "`-lt`" と "`-le`" と "`-gt`" と "`-ge`" です。

### 12.1.4. シェルループ

POSIX シェル中で使われるループの慣用句があります。

- "`for x in foo1 foo2 … ; do コマンド ; done`" は "`foo1 foo2 …`" リストの項目を変数 "`x`" に代入し "`コマンド`" を実行してループします。

- "`while 条件 ; do コマンド ; done`" は"`条件`" が真の場合 "`コマンド`" を繰り返します。

- "`until 条件 ; do コマンド ; done`" は"`条件`" が真でない場合 "`コマンド`" を繰り返します。

- "`break`" によってループから脱出できます。

- "`continue`" によってループ初めに戻り次の反復実行を再開します。

| ヒント
C 言語のような数字の繰り返しは "`foo1 foo2 ...`" 生成に seq(1) 使って実現します。

| ヒント
「ファイルに関してループしながらコマンドを反復実行」を参照下さい。

### 12.1.5. シェル環境変数

普通の人気あるシェル コマンド プロンプトがあなたのスクリプト実行環境下使えないかもしれません。

- "`$USER`" には "`$(id -un)`" を使います

- "`$UID`" には "`$(id -u)`" を使います

- "`$HOME`" に関して、"`$(getent passwd "$(id -u)"|cut -d ":" -f 6)`" を使います (これは「集中システム管理」上でも機能します。)

### 12.1.6. シェルコマンドライン処理シーケンス

シェルはおおよそ以下のシーケンスでスクリプトを処理します。

- シェルは1行読み込みます。

- シェルは、もし `"…"` や `'…'` の中なら、行の一部を1つのトークンとしてグループします。

- シェルは1行を以下のによってトークンに分割します。

  - 空白: `space tab newline`

  - メタ文字: `< > | ; & ( )`

- `"…"` や `'…'` の中でない場合、シェルは各トークンを予約語に対してチェックしその挙動を調整します。

  - 予約語: `if then elif else fi for in while unless do done case esac`

- `"…"` や `'…'` の中でない場合、シェルはエイリアスを展開します。

- `"…"`や`'…'`の中でない場合、シェルはティルダを展開します。

  - "`~`" → 現ユーザーのホームディレクトリー

  - "`~user`" → `user` のホームディレクトリー

- `'…'` の中でない場合、シェルは パラメーター"をその値に展開します。

  - パラメーター: "`$PARAMETER`" or "`${PARAMETER}`"

- `'…'` の中でない場合、シェルは コマンド置換を展開します。

  - "`$( command )`" → "`command`" の出力

  - "`` command ``" → "`command`" の出力

- `"…"` や `'…'` の中でない場合、シェルは パス名のグロブを展開します。

  - `*` → あらゆる文字

  - `?` → 1文字

  - `[…]` → "`…`" 中の1つ

- シェルはコマンドを次から検索して実行します。

  - 関数定義

  - ビルトインコマンド

  - "`$PATH`" 中の実行ファイル

- シェルは次行に進みこのプロセスを一番上から順に反復します。

ダブルクォート中のシングルクォートに効果はありません。

シェル環境中で "`set -x`" を実行したり、シェルを "`-x`" オプションで起動すると、シェルは実行するコマンドを全てプリントするようになります。これはデバグをするのに非常に便利です。

### 12.1.7. シェルスクリプトのためのユーティリティープログラム

Debian システム上でできるだけポータブルなシェルプログラムとするには、ユーティリティープログラムを essential パッケージで提供されるプログラムだけに制約するのが賢明です。

- "`aptitude search ~E`" はessential (必須) パッケージをリストします。

- "`dpkg -L パッケージ名 |grep '/man/man.*/'`" は `パッケージ名` パッケージによって提供されるコマンドのマンページをリストします。

表12.7 シェルスクリプト用の小さなユーティリティープログラムを含むパッケージのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`dash` | V:928, I:998 | 207 | `sh` 用の小型で高速の POSIX 準拠シェル
`coreutils` | V:910, I:1000 | 17994 | GNU コアユーティリティー
`grep` | V:779, I:1000 | 1297 | GNU `grep`, `egrep` and `fgrep`
`sed` | V:762, I:1000 | 987 | GNU `sed`
`mawk` | V:485, I:998 | 295 | 小さく高速な `awk`
`debianutils` | V:936, I:997 | 225 | Debian 特有の雑多なユーティリティー
`bsdutils` | V:445, I:1000 | 338 | 4.4BSD-Lite 由来の基本ユーティリティー
`bsdextrautils` | V:754, I:863 | 382 | 4.4BSD-Lite 由来の追加のユーティリティー
`moreutils` | V:18, I:39 | 232 | 追加の Unix ユーティリティー

| ヒント
`moreutils` は Debian の外では存在しないかも知れませんが、興味深い小さなプログラムを提供します。もっとも特記すべきは、オリジナルファイルを上書きしたいときに非常に有効な sponge(8) です。

例は「Unix 的テキスト処理」を参照下さい。

## 12.2. インタープリター言語でのスクリプティング

表12.8 インタープリター関連のパッケージのリスト

パッケージ | ポプコン | サイズ | 文書
--- | --- | --- | ---
`dash` | V:928, I:998 | 207 | sh: `sh` 用の小型高速 POSIX 準拠シェル
`bash` | V:893, I:1000 | 7309 | sh: `bash-doc` が提供する "`info bash`"
`mawk` | V:485, I:998 | 295 | AWK: 小型高速 `awk`
`gawk` | V:250, I:301 | 3289 | AWK: `gawk-doc` が提供する "`info gawk`"
`perl` | V:673, I:992 | 836 | Perl: `perl-doc` and `perl-doc-html`が提供する perl(1) と html ページ
`libterm-readline-gnu-perl` | V:2, I:27 | 439 | GNU ReadLine/History ライブラリーのための Perl 拡張: perlsh(1)
`libreply-perl` | V:0.01, I:0.10 | 171 | PerlのREPL: reply(1)
`libdevel-repl-perl` | V:0.03, I:0.57 | 237 | PerlのREPL: re.pl(1)
`python3` | V:717, I:974 | 80 | Python: `python-doc` が提供する python3(1) と html ページ
`tcl` | V:23, I:176 | 20 | Tcl: `tcl-doc` が提供する tcl(3) と詳細なマンページ
`tk` | V:17, I:169 | 20 | Tk: `tk-doc` が提供する tk(3) と詳細なマンページ
`ruby` | V:71, I:157 | 32 | Ruby: ruby(1), erb(1), irb(1), rdoc(1), ri(1)

Debian 上のタスクを自動化したい場合には、まず最初にインタープリター言語でタスクをスクリプト化すべきです。 インタープリター言語選択のガイドラインは:

- もしタスクがシェルプログラムでできた CLI プログラムを組み合わせる簡単なタスクなら、`dash` を使います。

- もしタスクが簡単なタスクではなく何もないところから書くなら、`python3` を使います。

- もしタスクをするための加筆をする必要がある `perl`, `tcl`, `ruby`, ... で書かれたコードが Debian 上にすでに存在する場合には、その言語を使います。

もし出来上がったコードがおそすぎる場合には、実行速度にクリチカルな部分のみコンパイラー言語で書き直しインタープリター言語から呼びます。

### 12.2.1. インタープリター言語コードのデバグ

ほとんどのインタープリターは基本的文法チェックやコード追跡の機能を提供します。

- “dash -n script.sh” - シェルスクリプトの文法チェック

- “dash -x script.sh” - シェルスクリプトのトレース

- “python -m py_compile script.py” - Python スクリプトの文法チェック

- “python -mtrace --trace script.py” - Python スクリプトのトレース

- “perl -I ../libpath -c script.pl” - Perl スクリプトの文法チェック

- “perl -d:Trace script.pl” - Perl スクリプトのトレース

`dash` のコードチェックの際には、 `bash` のようなインタラクティブ環境を用意する「Readline のラッパー」を試します。

`perl` のコードチェックの際には、Pythonのような REPL (=READ + EVAL + PRINT + LOOP) 環境を提供するPerl 用の環境を試しましょう。

### 12.2.2. シェルスクリプトを使った GUI プログラム

シェルスクリプトは魅力的な GUI プログラムを作るまで改善できます。`echo` や `read` コマンドを使う鈍いやりとりに代えて、いわゆるダイアログプログラムを使うのがこのトリックです。

表12.9 ダイアログプログラムのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`x11-utils` | V:222, I:551 | 651 | xmessage(1): window 中にメッセージや質問を表示 (X)
`whiptail` | V:303, I:997 | 61 | シェルスクリプトからユーザーフレンリーなダイアログボックスを表示 (newt)
`dialog` | V:8, I:77 | 520 | シェルスクリプトからユーザーフレンリーなダイアログボックスを表示 (ncurses)
`zenity` | V:74, I:338 | 193 | シェルスクリプトからグラフィカルなダイアログボックスを表示 (GTK)
`ssft` | V:0.01, I:0.16 | 80 | シェルスクリプトフロントエンドツール (gettext を使った zenity や kdialog や dialog のラッパー)
`gettext` | V:51, I:216 | 20468 | "`/usr/bin/gettext.sh`": メッセージ翻訳

簡単なシェルスクリプトだけでできるほど GUI プログラムがいかに簡単ということを示す GUI プログラムの例は以下です。

このスクリプトはファイルの選択(デフォルトは`/etc/motd`) に `zenity` を使い、それを表示します。

このスクリプトの GUI ローンチャーは以下のようにして生成できます 「GUI からプログラムをスタート」.

    #!/bin/sh -e
    # Copyright (C) 2021 Osamu Aoki <osamu@debian.org>, Public Domain
    # vim:set sw=2 sts=2 et:
    DATA_FILE=$(zenity --file-selection --filename="/etc/motd" --title="Select a file to check") || \
      ( echo "E: File selection error" >&2 ; exit 1 )
    # Check size of archive
    if ( file -ib "$DATA_FILE" | grep -qe '^text/' ) ; then
      zenity --info --title="Check file: $DATA_FILE" --width 640  --height 400 \
        --text="$(head -n 20 "$DATA_FILE")"
    else
      zenity --info --title="Check file: $DATA_FILE" --width 640  --height 400 \
        --text="The data is MIME=$(file -ib "$DATA_FILE")"
    fi

シェル スクリプトでの GUI プログラムへのこのようなアプローチは簡単な選択ケースでのみ有効です。何らかの複雑のあるプログラムを書く場合には、もっと能力あるプラットフォームで書くことを考えましょう。

### 12.2.3. GUI フィルター用のカスタム動作集

GUI ファイラー プログラムは、追加の拡張パッケージを使って選択されたファイルに対していくつかの人気ある動作を実行するように拡張できます。また、GUI ファイラー プログラムはあなたの特定のスクリプトを追加することで非常に特定のカスタム動作を実行するようにもできます。

- GNOME の場合、NautilusScriptsHowtoを参照下さい。

- KDE の場合、Creating Dolphin Service Menusを参照下さい。

- Xfce の場合、Thunar - Custom Actions and https://help.ubuntu.com/community/ThunarCustomActionsを参照下さい。

- LXDEの場合、Custom Actionsを参照下さい。

### 12.2.4. 究極の短い Perl スクリプト

データー処理をするためには、`cut` や `grep` や `sed` 等を実行するサブプロセスを起動する必要が `sh` ではあり、遅いです。一方、データーを処理する内部機能が `perl` ではあり、高速です。そのため、多くの Debian のシステム メンテナンス スクリプトは `perl` を使います。

以下の AWK スクリプトとそれと等価Perl スクリプトの断片を考えましょう。

    awk '($2=="1957") { print $3 }' |

これは以下の数行のどれとも等価です。

    perl -ne '@f=split; if ($f[1] eq "1957") { print "$f[2]\n"}' |

    perl -ne 'if ((@f=split)[1] eq "1957") { print "$f[2]\n"}' |

    perl -ne '@f=split; print $f[2] if ( $f[1]==1957 )' |

    perl -lane 'print $F[2] if $F[1] eq "1957"' |

    perl -lane 'print$F[2]if$F[1]eq+1957' |

最後のスクリプトは謎々状態です。Perl の以下の機能を利用しています。

- ホワイトスペースはオプション。

- 数字から文字列への自動変換が存在します。

- コマンドライン オプション経由の Perl 実行トリック集: perlrun(1)

- Perl の特別な変数集: perlvar(1)

このフレキシビリティーは Perl の強みです。同時に、これは我々に読解不能な絡まったコードを書くことを許容します。だから注意して下さい。

## 12.3. コンパイル言語でのコーディング

表12.10 コンパイラ関連のパッケージのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`gcc` | V:155, I:564 | 36 | GNU C コンパイラ
`libc6-dev` | V:269, I:584 | 12861 | GNU C ライブラリー: 開発用ライブラリーとヘッダーファイル集
`g++` | V:57, I:531 | 13 | GNU C++ コンパイラー
`libstdc++-16-dev` | V:0.7, I:4.3 | 24382 | GNU 標準 C++ ライブラリー v3 (開発用ファイル集)
`cpp` | V:332, I:719 | 18 | GNU C プリプロセッサ
`gettext` | V:51, I:216 | 20468 | GNU 国際化ユーティリティ
`glade` | V:0.7, I:2.7 | 1613 | GTK ユーザー インターフェース ビルダー
`valac` | V:0.2, I:3.2 | 533 | GObject システム用の C# に似た言語
`flex` | V:7, I:68 | 1247 | LEX 互換の 高速字句解析ジェネレータ
`bison` | V:7, I:73 | 3122 | YACC互換のパーサジェネレータ
`susv2` | I:0.03 | 16 | "The Single UNIX Specifications v2" を取得
`susv3` | I:0.06 | 16 | "The Single UNIX Specifications v3" を取得
`susv4` | I:0.04 | 16 | "The Single UNIX Specifications v4" を取得
`golang` | I:22 | 12 | Go プログラミング言語コンパイラー
`rustc` | V:5, I:20 | 13061 | Rust システム プログラム言語
`gfortran` | V:4, I:50 | 15 | GNU Fortran 95 コンパイラー
`fpc` | I:2.3 | 101 | Free Pascal

ここで、「Flex — 改良版 Lex」と「Bison — 改良版 Yacc」は、コンパイラーのようなプログラムがどのように高レベル記述を C 言語にすることで C 言語で書かれているかを示すために含めています。

### 12.3.1. C

C プログラム言語で書かれたプログラムをコンパイルする適正な環境を以下のようにして設定できます。

    # apt-get install glibc-doc manpages-dev libc6-dev gcc build-essential

GNU C ライブラリーパッケージである `libc6-dev` パッケージは、C プログラム言語で使われるヘッダーファイルやライブラリールーチンの集合である C 標準ライブラリーを提供します。

C のリファレンスは以下を参照下さい。

- "`info libc`" (C ライブラリー関数リファレンス)

- gcc(1) と "`info gcc`"

- `各 C ライブラリー関数名`(3)

- Kernighan & Ritchie 著, "The C Programming Language", 第2版 (Prentice Hall)

### 12.3.2. 単純な C プログラム (gcc)

簡単な例の "`example.c`" は"`libm`" ライブラリーを使って実行プログラム "`run_example`" に以下のようにしてコンパイル出来ます。

    $ cat > example.c << EOF
    #include <stdio.h>
    #include <math.h>
    #include <string.h>

    int main(int argc, char **argv, char **envp){
            double x;
            char y[11];
            x=sqrt(argc+7.5);
            strncpy(y, argv[0], 10); /* prevent buffer overflow */
            y[10] = '\0'; /* fill to make sure string ends with '\0' */
            printf("%5i, %5.3f, %10s, %10s\n", argc, x, y, argv[1]);
            return 0;
    }
    EOF
    $ gcc -Wall -g -o run_example example.c -lm
    $ ./run_example
            1, 2.915, ./run_exam,     (null)
    $ ./run_example 1234567890qwerty
            2, 3.082, ./run_exam, 1234567890qwerty

ここで、"`-lm`" はsqrt(3) のために `libc6` パッケージで提供されるライブラリー "`/usr/lib/libm.so`" をリンクするのに必要です。実際のライブラリーは "`/lib/`" 中にあるファイル名 "`libm.so.6`" で、それは "`libm-2.7.so`" にシムリンクされています。

出力テキスト中の最後のパラメーターを良く見ましょう。"`%10s`" が指定されているにもかかわらず10文字以上あります。

上記のオーバーラン効果を悪用するバッファーオーバーフロー攻撃を防止のために、sprintf(3) や strcpy(3) 等の境界チェック無しのポインターメモリー操作関数の使用は非推奨です。これに代えて snprintf(3) や strncpy(3) を使います。

### 12.3.3. Flex — 改良版 Lex

Flex はLex 互換の高速字句解析生成ソフトです。

flex(1) の入門書は "`info flex`" の中にあります。

シンプルな例が "`/usr/share/doc/flex/examples/`" の下にあります。 [7]

### 12.3.4. Bison — 改良版 Yacc

Yacc 互換の前方参照可能な LR パーサーとか LALR パーサー生成ソフトは、いくつかのパッケージによって Debian 上で提供されています。

表12.11 Yacc 互換の LALR パーサー生成ソフトのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`bison` | V:7, I:73 | 3122 | GNU LALR パーサー生成ソフト
`byacc` | V:0.1, I:3.0 | 263 | Berkeley LALR パーサー生成ソフト
`btyacc` | V:0.01, I:0.06 | 247 | `byacc` に基づいたバックトラッキング機能付きパーサー生成ソフト

bison(1) の入門書は "`info bison`" の中にあります。

あなた自身の "`main()`" と "`yyerror()`" を供給する必要があります。"`main()`" は、Flex によって通常作成された "`yylex()`" を呼び出す "`yyparse()`" を呼び出します。

シンプルなターミナル上の計算機プログラムの作成例をここに示します。

`example.y` を作成しましょう:

    /* calculator source for bison */
    %{
    #include <stdio.h>
    extern int yylex(void);
    extern int yyerror(char *);
    %}

    /* declare tokens */
    %token NUMBER
    %token OP_ADD OP_SUB OP_MUL OP_RGT OP_LFT OP_EQU

    %%
    calc:
     | calc exp OP_EQU    { printf("Y: RESULT = %d\n", $2); }
     ;

    exp: factor
     | exp OP_ADD factor  { $$ = $1 + $3; }
     | exp OP_SUB factor  { $$ = $1 - $3; }
     ;

    factor: term
     | factor OP_MUL term { $$ = $1 * $3; }
     ;

    term: NUMBER
     | OP_LFT exp OP_RGT  { $$ = $2; }
      ;
    %%

    int main(int argc, char **argv)
    {
      yyparse();
    }

    int yyerror(char *s)
    {
      fprintf(stderr, "error: '%s'\n", s);
    }

`example.l` を作成しましょう:

    /* calculator source for flex */
    %{
    #include "example.tab.h"
    %}

    %%
    [0-9]+ { printf("L: NUMBER = %s\n", yytext); yylval = atoi(yytext); return NUMBER; }
    "+"    { printf("L: OP_ADD\n"); return OP_ADD; }
    "-"    { printf("L: OP_SUB\n"); return OP_SUB; }
    "*"    { printf("L: OP_MUL\n"); return OP_MUL; }
    "("    { printf("L: OP_LFT\n"); return OP_LFT; }
    ")"    { printf("L: OP_RGT\n"); return OP_RGT; }
    "="    { printf("L: OP_EQU\n"); return OP_EQU; }
    "exit" { printf("L: exit\n");   return YYEOF; } /* YYEOF = 0 */
    .      { /* ignore all other */ }
    %%

そして、これを試すためにシェルプロンプトから以下を実行しましょう:

    $ bison -d example.y
    $ flex example.l
    $ gcc -lfl example.tab.c lex.yy.c -o example
    $ ./example
    1 + 2 * ( 3 + 1 ) =
    L: NUMBER = 1
    L: OP_ADD
    L: NUMBER = 2
    L: OP_MUL
    L: OP_LFT
    L: NUMBER = 3
    L: OP_ADD
    L: NUMBER = 1
    L: OP_RGT
    L: OP_EQU
    Y: RESULT = 9

    exit
    L: exit

## 12.4. 静的コード分析ツール

静的解析(lint) のようなツールは、自動静的コード分析に役立ちます。

Indent のようなツールは整合性をもってソースコードの再フォーマットすることで人間によるコードレビューを助けます。

Ctags のようなツールは、ソースコード中に見つかる名前のインデックス(とかタグ)を生成することで、人間がコードのレビューするのを助けます。

| ヒント
あなたの好きなエディター (`emacs` か `vim`) を非同期の lint エンジン プラグインを使うように設定することはあなたがコードを書く際に役立ちます。このようなプラグインは Language Server Protocol を利用することで非常にパワフルになっています。プラグインは非常に変化の激しいので、Debian パッケージではなくアップストリーム コードを使うのは良い方策かもしれません。

表12.12 静的コード分析ツールのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`vim-ale` | I:0.80 | 2833 | Vim 8 や NeoVim 用の非同期静的解析エンジン
`vim-syntastic` | I:2.0 | 1379 | Vim 用のシンタックスシェックのハック
`elpa-flycheck` | V:0.1, I:1.7 | 1130 | Emacs 用の現代的な同時進行のシンタックスチェック
`elpa-relint` | I:0.06 | 150 | Emacs Lisp の正規表現 (regexp) の間違い検出器
`cppcheck-gui` | V:0.1, I:1.0 | 7697 | 静的 C/C++ コード分析ツール (GUI)
`shellcheck` | V:3, I:16 | 22860 | シェルスクリプトの静的解析(lint)ツール
`pyflakes3` | V:1, I:12 | 20 | Python 3 の受動チェッカー
`pylint` | V:3, I:17 | 2093 | Python コード静的チェックソフト
`perl` | V:673, I:992 | 836 | 静的コードチェックソフト付きのインタープリタ: `B::Lint`(3perl)
`rubocop` | V:0.1, I:1.1 | 4192 | Ruby 静的コード分析ツール
`clang-tidy` | V:2, I:10 | 21 | Clang に基づく C++ の静的解析(lint)ツール
`splint` | V:0.06, I:0.92 | 2325 | C プログラムを静的にバグのチェックするためのツール
`flawfinder` | V:0.07, I:0.49 | 187 | C/C++ ソースコードを検査してセキュリティーの脆弱性を探すツール
`black` | V:2, I:13 | 10057 | 非妥協的な Python コードフォーマッター
`perltidy` | V:0.4, I:3.1 | 3086 | Perl スクリプトのインデントとリフォーマット
`indent` | V:0.8, I:5.1 | 438 | C 言語ソースコード フォーマッター プログラム
`astyle` | V:0.2, I:2.7 | 770 | C と C++ と Objective-C と C# と Java のソースコード インデンター
`bcpp` | V:0.02, I:0.26 | 114 | C(++) の美化プログラム
`xmlindent` | V:0.05, I:0.79 | 52 | XML ストリームリフォーマッタ
`global` | V:0.2, I:1.6 | 1923 | ソースコードの検索と閲覧のツール
`exuberant-ctags` | V:2, I:13 | 341 | ソースコード定義のタグファイルのインデックスの構築
`universal-ctags` | V:2, I:10 | 4238 | ソースコード定義のタグファイルのインデックスの構築

## 12.5. デバグ

デバッグはプログラミング活動において重要です。プログラムのデバッグ法を知ることで、あなたも意味あるバグリポートを作成できるような良い Debian ユーザーになれます。

表12.13 デバッグパッケージのリスト

パッケージ | ポプコン | サイズ | 文書
--- | --- | --- | ---
`gdb` | V:88, I:161 | 12453 | `gdb-doc` が提供する "`info gdb`"
`ddd` | V:0.7, I:5.2 | 4210 | `ddd-doc` が提供する "`info ddd`"

### 12.5.1. 基本的な gdb 実行

Debian 上の第一義的デバッガは、実行中のプログラムを検査できるようにする gdb(1) です。

`gdb` と関連プログラムを以下のようにインストールしましょう。

    # apt-get install gdb gdb-doc build-essential devscripts

`gdb` のよいチュートリアルについては以下を参照下さい:

- “`info gdb`”

- `/usr/share/doc/gdb-doc/html/gdb/index.html` 中の “Debugging with GDB”

- “ウェブ上のチュートリアル”

次は gdb(1) を"`-g`" を使ってデバッグ情報を付けてコンパイルされた "`program`" に使う簡単な例です。

    $ gdb program
    (gdb) b 1                # set break point at line 1
    (gdb) run args           # run program with args
    (gdb) next               # next line
    ...
    (gdb) step               # step forward
    ...
    (gdb) p parm             # print parm
    ...
    (gdb) p parm=12          # set value to 12
    ...
    (gdb) quit

| ヒント
多くの gdb(1) コマンドは省略できます。タブ展開はシェル同様に機能します。

### 12.5.2. Debian パッケージのデバグ

全てのインストールされたバイナリーはデフォルトでは Debian システム上ではストリップされているべきなので、ほとんどのデバグシンボルは普通のパッケージからは除かれています。gdb(1) を使って Debian パッケージをデバグするためには、`*-dbgsym` パッケージ(例えば`coreutils` の場合は `coreutils-dbgsym`)をインストールする必要があります。ソースパッケージは、普通のバイナリーパッケージとともに `*-dbgsym` パッケージを自動生成し、そうしたデバグパッケージは別にして debian-debug アーカイブ中に置かれます。詳細は Debian Wiki の記事を参照下さい。

デバッグしようとしているパッケージに`*-dbgsym` パッケージが無い場合は、以下のようにしてリビルドした後でインストールする必要があります。

    $ mkdir /path/new ; cd /path/new
    $ sudo apt-get update
    $ sudo apt-get dist-upgrade
    $ sudo apt-get install fakeroot devscripts build-essential
    $ apt-get source package_name
    $ cd package_name*
    $ sudo apt-get build-dep ./

必要に応じてバグを修正します。

例えば以下のように、既存パッケージを再コンパイルする時は "`+debug1`" を後ろに付けたり、リリース前のパッケージをコンパイルする時は "`~pre1`" を後ろに付けたりと、正規の Debian バージョンとかち合わないようにパッケージバージョンを増やします。

    $ dch -i

以下のようにしてデバグシンボル付きでパッケージをコンパイルしてインストールします。

    $ export DEB_BUILD_OPTIONS="nostrip noopt"
    $ debuild
    $ cd ..
    $ sudo debi package_name*.changes

パッケージのビルドスクリプトを確認して、バイナリーのコンパイルに確実に "`CFLAGS=-g -Wall`" が使われているようにします。

### 12.5.3. バックトレースの収集

プログラムがクラッシュするのに出会った場合に、バックトレース情報をバグレポートに切り貼りして報告するのは良い考えです。

バックトレースはgdb(1) によって以下のような段取りで得られます。

- GDBの中でクラッシュ アプローチ:

  - GDB からプログラムを実行します。

  - プログラムがクラッシュします。

  - GDB プロンプトで "`bt`" と打ちます。

- 先にクラッシュ アプローチ:

  - “/etc/security/limits.conf” ファイルを更新して以下を含めます:

        * soft core unlimited

  - シェルプロンプトで "`ulimit -c unlimited`" と打ちます。

  - このシェルプロンプトからプログラムを実行します。

  - プログラムがクラッシュしてコアダンプファイルができます。

  - "`gdb gdb ./program_binary core`" として core dump ファイルを GDB にロードします。

  - GDB プロンプトで "`bt`" と打ちます。

無限ループやキーボードが凍結した場合、`Ctrl-\` か `Ctrl-C` を押すか “`kill -ABRT PID`” を実行することでプログラムを強制終了できます。(「プロセスの停止」を参照下さい)

| ヒント

しばしば、一番上数行が "`malloc()`" か "`g_malloc()`" 中にあるバックトレースを見かけます。こういったことが起こる場合は、大体あまりあなたのバックトレースは役に立ちません。有用な情報を見つけるもっとも簡単な方法は環境変数 "`$MALLOC_CHECK_`" の値を 2と設定することです (malloc(3))。`gdb` を実行しながらこれを実行するには以下のようにします。

     $ MALLOC_CHECK_=2 gdb hello

### 12.5.4. 高度な gdb コマンド

表12.14 高度な gdb コマンドのリスト

コマンド | コマンド目的の説明
--- | ---
`(gdb) thread apply all bt` | マルチスレッドプログラムの全てのスレッドのバックトレースを取得
`(gdb) bt full` | 関数コールのスタック上に来たパラメーターを取得
`(gdb) thread apply all bt full` | 異常のオプションの組み合わせでバックトレースとパラメーターを取得
`(gdb) thread apply all bt full 10` | 無関係の出力を切り最後の10のコールに関するバックトレースとパラメーターを取得
`(gdb) set logging on` | `gdb` アウトプットをファイルに書き出す (デフォールトは "`gdb.txt`")

### 12.5.5. ライブラリーへの依存の確認

以下のように ldd(1) を使ってプログラムのライブラリーへの依存関係をみつけだします。

    $ ldd /usr/bin/ls
            librt.so.1 => /lib/librt.so.1 (0x4001e000)
            libc.so.6 => /lib/libc.so.6 (0x40030000)
            libpthread.so.0 => /lib/libpthread.so.0 (0x40153000)
            /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

`chroot` された環境下で ls(1) が機能するには、上記ライブラリーがあなたの `chroot` された環境内で利用可能である必要があります。

「プログラム活動の追跡」を参照下さい。

### 12.5.6. 動的呼び出し追跡ツール

Debian 中に複数の動的呼び出し追跡ツールがあります。「プログラム活動の監視と制御と起動」 を参照下さい。

### 12.5.7. X エラーのデバグ

GNOME プログラム `preview1` が X エラーを受けると、以下のようなメッセージが見つかります。

    The program 'preview1' received an X Window System error.

このような場合には、プログラムを "`--sync`" 付きで実行して、バックトレースを得るために "`gdk_x_error`" 関数上で停止するようにしてみます。

### 12.5.8. メモリーリーク検出ツール

Debian にはメモリーリークを検出するプログラムがいくつか利用可能です。

表12.15 メモリーリーク検出ツールのリスト

パッケージ | ポプコン | サイズ | 説明
--- | --- | --- | ---
`libc6-dev` | V:269, I:584 | 12861 | mtrace(1): glibc 中の malloc デバッグ機能
`valgrind` | V:4, I:31 | 89318 | メモリーデバッガとプロファイラ
`electric-fence` | V:0.2, I:2.3 | 69 | malloc(3) デバッガ
`libdmalloc5` | V:0.01, I:0.61 | 373 | メモリーアロケーションのデバグ用ライブラリー
`duma` | V:0.00, I:0.06 | 297 | C および C++ プログラムのバッファオーバーランとアンダーランを検出するライブラリー
`leaktracer` | V:0.01, I:0.33 | 56 | C++ プログラム用のメモリーリーク追跡ソフト

### 12.5.9. バイナリーのディスアッセンブリー

以下のように objdump(1) を使ってバイナリーコードをディスアッセンブルできます。

    $  objdump -m i386 -b binary -D /usr/lib/grub/x86_64-pc/stage1

| 注記
gdb(1) は対話的にコードをディスアッセンブルするのに使えます。

## 12.6. ビルドツール

表12.16 ビルドツールパッケージのリスト

パッケージ | ポプコン | サイズ | 文書
--- | --- | --- | ---
`make` | V:150, I:566 | 1762 | `make-doc` が提供する "`info make`"
`autoconf` | V:29, I:200 | 2244 | `autoconf-doc` が提供する "`info autoconf`
`automake` | V:28, I:199 | 1932 | `automake1.10-doc` が提供する "`info automake`"
`libtool` | V:24, I:183 | 1245 | `libtool-doc` が提供する "`info libtool`"
`cmake` | V:21, I:123 | 59735 | クロスプラットフォームでオープンソースの make システム cmake(1)
`ninja-build` | V:9, I:55 | 456 | Make と似た精神の小さなビルドシステム ninja(1)
`meson` | V:6, I:29 | 4278 | `ninja` の上に構築された高生産性のビルドシステム meson(1)
`xutils-dev` | V:0.9, I:7.0 | 1495 | imake(1), xmkmf(1), 他

### 12.6.1. Make

Make はプログラムのグループを管理するためのユーティリティーです。make(1) を実行すると、`make` は"`Makefile`" というルールファイルを読み、ターゲットが最後に変更された後で変更された前提ファイルにターゲットが依存している場合やターゲットが存在しない場合にはターゲットを更新します。このような更新は同時並行的にされるかもしれません。

ルールファイルのシンタックスは以下の通りです。

    target: [ prerequisites ... ]
     [TAB]  command1
     [TAB]  -command2 # ignore errors
     [TAB]  @command3 # suppress echoing

上記で、"`[TAB]`" は TAB コードです。各行は make による変数置換後シェルによって解釈されます。スクリプトを継続する行末には "`\`" を使います。シェルスクリプトの環境変数のための "`$`" を入力するためには "`$$`" を使います。

ターゲットや前提に関するインプリシット (暗黙) ルールは、例えば以下のように書けます。

    %.o: %.c header.h

上記で、ターゲットは "`%`" という文字を (1つだけ) 含んでいます。"`%`" は実際のターゲットファイル名の空でないいかなる部分文字列ともマッチします。前提もまた同様にそれらの名前が実際のターゲットファイル名にどう関連するかを示すために "`%`" を用いることができます。

表12.17 make の自動変数のリスト

自動変数 | 変数値
--- | ---
`$@` | ターゲット
`$<` | 最初の前提条件
`$?` | 全ての新規の前提条件
`$^` | 全ての前提条件
`$*` | "`%`" はターゲットパターンの軸にマッチします

表12.18 make 変数の展開のリスト

変数展開 | 説明
--- | ---
`foo1 := bar` | 一回だけの展開
`foo2 = bar` | 再帰的展開
`foo3 += bar` | 後ろに追加

"`make -p -f/dev/null`" を実行して自動的な内部ルールを確認下さい。

### 12.6.2. Autotools

Autotools は多くの Unix-like システムに移植可能なソースコードパッケージを作ることを援助するように設計された一連のプログラムツールです。

- Autoconf は "`configure.ac`" から "`configure`" を生成します。

  - その後、"`configure`" は "`Makefile.in`" から "`Makefile`" を生成します。

- Automake は "`Makefile.am`" から "`Makefile.in`" を生成します。

- Libtool は共有ライブラリーをソースコードからコンパイルする時のソフトウェアー移植性問題を解決するためのシェルプログラムです。

#### 12.6.2.1. プログラムをコンパイルとインストール

| 警告
システムファイルをあなたがコンパイルしたプログラムでインストールする時に上書きしてはいけません。

Debian は"`/usr/local/`" とか "`/opt`" 中のファイルに触れません。プログラムをソースからコンパイルする場合、Debian とかち合わないようにそれを "`/usr/local/`" の中にインストールします。

    $ cd src
    $ ./configure --prefix=/usr/local
    $ make # this compiles program
    $ sudo make install # this installs the files in the system

#### 12.6.2.2. プログラムのアンインストール

オリジナルのソースを保有し、それが autoconf(1)/automake(1) と使用しあなたがそれをどう設定したかを覚えているなら、以下のように実行してソフトウェアーをアンイストールします。

    $ ./configure all-of-the-options-you-gave-it
    $ sudo make uninstall

この代わりに、"`/usr/local/`" の下にだけインストールプロセスがファイルを置いたことが絶対に確実でそこに重要なものが無いなら、以下のようにしてその内容を消すことが出来ます。

    # find /usr/local -type f -print0 | xargs -0 rm -f

どこにファイルがインストールされるか良く分からない場合には、`checkinstall` パッケージにある checkinstall(8) を使いアンインストールする場合クリーンなパスとなるようにすることを考えましょう。これは "`-D`" オプションを使うと Debian パッケージを作成できます。

### 12.6.3. Meson

ソフトウェアービルドシステムは進化してきています:

- Make の上に構築された Autotools は 1990 年代より移植可能なビルドインフラのデファクトスタンダードです。これは非常に遅いです。

- 2000 年に最初にリリースされた CMake は、スピードが大幅向上させたが、依然として本質的に遅い Make の上に構築されていました。(現在は、Ninja がバックエンドで使えます。)

- 2012 年に最初にリリースされた Ninja は、さらなるビルド速度の向上のために Make の置き換えを意図し、その入力ファイルはハイレベルビルドシステムが生成するように設計されています。

- 2013 年に最初にリリースされた Meson は、新しく人気ある Ninja をバックエンドに使うハイレベルビルドシステムです。

"The Meson Build system" や "The Ninja build system" にある文書を参照下さい。

## 12.7. ウェッブ

基本的な対話式動的ウェッブページは以下のようにして作られます。

- 質問 (クエリー) はブラウザーのユーザーに HTML フォームを使って提示されます。

- フォームのエントリーを埋めたりクリックすることによって以下の符号化されたパラメーター付きの URL 文字列をブラウザーからウェッブサーバーに送信します。

  - "`https://www.foo.dom/cgi-bin/program.pl?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3`"

  - "`https://www.foo.dom/cgi-bin/program.py?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3`"

  - "`https://www.foo.dom/program.php?VAR1=VAL1&VAR2=VAL2&VAR3=VAL3`"

- URL 中の "`%nn`" は16進数で `nn` の値の文字と置き換えられます。

- 環境変通は以下のように設定されます: "`QUERY_STRING="VAR1=VAL1 VAR2=VAL2 VAR3=VAL3"`".

- ウェッブサーバー上の CGI プログラム ("`program.*`" のいずれでも) が環境変数 "`$QUERY_STRING`" の下で実行されます。

- CGI プログラムの `STDOUT` (標準出力) がウエブブラウザーに送られ対話式の動的なウェッブページとして表示されます。

セキュリティー上、CGI パラメーターを解釈する手作りの急ごしらえのプログラムは作らない方が賢明です。Perl や Python にはこのための確立したモジュールが存在します。PHP はこの様な機能が同梱されています。クライアントでのデーターのストレージの必要がある場合、HTTP クッキーが使われます。クライアントサイドのデーター処理が必要な場合、Javascript が良く使われます。

詳しくは、Common Gateway Interface や The Apache Software Foundation や JavaScript を参照下さい。

https://www.google.com/search?hl=en&ie=UTF-8&q=CGI+tutorial を URL として直接ブラウザーのアドレスに入れ Google で"CGI tutorial" を検索するとグーグルサーバー上の CGI スクリプトが動いているのを観察する良い例です。

## 12.8. ソースコード変換

ソースコード変換するプログラムがあります。

表12.19 ソースコード変換ツールのリスト

パッケージ | ポプコン | サイズ | キーワード | 説明
--- | --- | --- | --- | ---
`perl` | V:673, I:992 | 836 | AWK→PERL | AWK から PERL へのソースコード変換シフト: a2p(1)
`f2c` | V:0.1, I:2.0 | 443 | FORTRAN→C | FORTRAN 77 から C/C++ へのソースコード変換ソフト: f2c(1)
`intel2gas` | V:0.02, I:0.19 | 178 | intel→gas | NASM (Intel フォーマット) から GNU Assembler (GAS) への変換ソフト

## 12.9. Debian パッケージ作成

Debian パッケージを作りたい場合には、次を読みましょう。

- 基本的なパッケージシステムの理解には2章Debian パッケージ管理

- 基本的なポーティングプロセスの理解のために、「安定版システムへのパッケージ移植」

- 基本的な chroot 技術の理解のために、「Chroot システム」

- debuild(1) と sbuild(1)

- リコンパイルとデバグは「Debian パッケージのデバグ」

- Guide for Debian Maintainers (`debmake-doc` パッケージ)

- Debian Developer's Reference (`developers-reference` パッケージ)

- Debian ポリシーマニュアル (`debian-policy` パッケージ)

`debmake` や `dh-make` や `dh-make-perl` 等のパッケージングを補助するパッケージがあります。

[7] 現行のシステム下でこれらを動かすには少々微調整が必要かもしれません。

第11章 データー変換 |  | 付録A 補遺