第12章 プログラミング

目次

12.1. シェルスクリプト
12.1.1. POSIX シェル互換性
12.1.2. シェル変数
12.1.3. シェル条件式
12.1.4. シェルループ
12.1.5. シェル環境変数
12.1.6. シェルコマンドライン処理シーケンス
12.1.7. シェルスクリプトのためのユーティリティープログラム
12.2. インタープリター言語でのスクリプティング
12.2.1. インタープリター言語コードのデバグ
12.2.2. シェルスクリプトを使った GUI プログラム
12.2.3. GUI フィルター用のカスタム動作集
12.2.4. 究極の短い Perl スクリプト
12.3. コンパイル言語でのコーディング
12.3.1. C
12.3.2. 単純な C プログラム (gcc)
12.3.3. Flex — 改良版 Lex
12.3.4. Bison — 改良版 Yacc
12.4. 静的コード分析ツール
12.5. デバグ
12.5.1. 基本的な gdb 実行
12.5.2. Debian パッケージのデバグ
12.5.3. バックトレースの収集
12.5.4. 高度な gdb コマンド
12.5.5. ライブラリーへの依存の確認
12.5.6. 動的呼び出し追跡ツール
12.5.7. X エラーのデバグ
12.5.8. メモリーリーク検出ツール
12.5.9. バイナリーのディスアッセンブリー
12.6. ビルドツール
12.6.1. Make
12.6.2. Autotools
12.6.2.1. プログラムをコンパイルとインストール
12.6.2.2. プログラムのアンインストール
12.6.3. Meson
12.7. ウェッブ
12.8. ソースコード変換
12.9. Debian パッケージ作成

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

オンラインリファレンスは manpagesmanpages-dev パッケージをインストールした後で "man name" とタイプすると利用可能です。GNU ツールのオンラインリファレンスは該当する文書パッケージをインストールした後で "info program_name" とタイプすると使えます。一部の GFDL 文書は DFSG に準拠していないと考えられているので main アーカイブに加えて contribnon-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 シェル間でポータブルとするために bashismszshisms を使うシェルスクリプトを書くのを避けます。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 file1file2 よりも新しい (変更)
file1 -ot file2 file1file2 よりも古い (変更)
file1 -ef file2 file1file2 は同デバイス上の同 inode 番号

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

論理真を返す条件
-z str str の長さがゼロ
-n str str の長さが非ゼロ
str1 = str2 str1str2 は等しい
str1 != str2 str1str2 は等しく無い
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:884, I:997 191 sh 用の小型で高速の POSIX 準拠シェル
coreutils V:880, I:999 18307 GNU コアユーティリティー
grep V:782, I:999 1266 GNU grep, egrep and fgrep
sed V:790, I:999 987 GNU sed
mawk V:442, I:997 285 小さく高速な awk
debianutils V:907, I:999 224 Debian 特有の雑多なユーティリティー
bsdutils V:519, I:999 356 4.4BSD-Lite 由来の基本ユーティリティー
bsdextrautils V:596, I:713 339 4.4BSD-Lite 由来の追加のユーティリティー
moreutils V:15, I:38 231 追加の Unix ユーティリティー

[ヒント] ヒント

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

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

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

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

パッケージ ポプコン サイズ 文書
dash V:884, I:997 191 sh: sh 用の小型高速 POSIX 準拠シェル
bash V:838, I:999 7175 sh: bash-doc が提供する "info bash"
mawk V:442, I:997 285 AWK: 小型高速 awk
gawk V:285, I:349 2906 AWK: gawk-doc が提供する "info gawk"
perl V:707, I:989 673 Perl: perl-doc and perl-doc-html が提供する perl(1) と html ページ
libterm-readline-gnu-perl V:2, I:29 380 GNU ReadLine/History ライブラリーのための Perl 拡張: perlsh(1)
libreply-perl V:0, I:0 171 PerlのREPL: reply(1)
libdevel-repl-perl V:0, I:0 237 PerlのREPL: re.pl(1)
python3 V:718, I:953 81 Python: python-doc が提供する python3(1) と html ページ
tcl V:25, I:218 21 Tcl: tcl-doc が提供する tcl(3) と詳細なマンページ
tk V:20, I:211 21 Tk: tk-doc が提供する tk(3) と詳細なマンページ
ruby V:86, I:208 29 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 プログラムを作るまで改善できます。echoread コマンドを使う鈍いやりとりに代えて、いわゆるダイアログプログラムを使うのがこのトリックです。

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

パッケージ ポプコン サイズ 説明
x11-utils V:192, I:566 651 xmessage(1): window 中にメッセージや質問を表示 (X)
whiptail V:284, I:996 56 シェルスクリプトからユーザーフレンリーなダイアログボックスを表示 (newt)
dialog V:11, I:99 1227 シェルスクリプトからユーザーフレンリーなダイアログボックスを表示 (ncurses)
zenity V:76, I:363 183 シェルスクリプトからグラフィカルなダイアログボックスを表示 (GTK)
ssft V:0, I:0 75 シェルスクリプトフロントエンドツール (gettext を使った zenity や kdialog や dialog のラッパー)
gettext V:56, I:259 5818 "/usr/bin/gettext.sh": メッセージ翻訳

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

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

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

#!/bin/sh -e
# Copyright (C) 2021 Osamu Aoki <[email protected]>, 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 ファイラー プログラムはあなたの特定のスクリプトを追加することで非常に特定のカスタム動作を実行するようにもできます。

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

データー処理をするためには、cutgrepsed 等を実行するサブプロセスを起動する必要が 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:167, I:550 36 GNU C コンパイラ
libc6-dev V:248, I:567 12053 GNU C ライブラリー: 開発用ライブラリーとヘッダーファイル集
g++ V:56, I:501 13 GNU C++ コンパイラー
libstdc++-10-dev V:14, I:165 17537 GNU 標準 C++ ライブラリー v3 (開発用ファイル集)
cpp V:334, I:727 18 GNU C プリプロセッサ
gettext V:56, I:259 5818 GNU 国際化ユーティリティ
glade V:0, I:5 1204 GTK ユーザー インターフェース ビルダー
valac V:0, I:4 725 GObject システム用の C# に似た言語
flex V:7, I:73 1243 LEX 互換の 高速字句解析ジェネレータ
bison V:7, I:80 3116 YACC互換のパーサジェネレータ
susv2 I:0 16 "The Single UNIX Specifications v2" を取得
susv3 I:0 16 "The Single UNIX Specifications v3" を取得
susv4 I:0 16 "The Single UNIX Specifications v4" を取得
golang I:20 11 Go プログラミング言語コンパイラー
rustc V:3, I:14 8860 Rust システム プログラム言語
haskell-platform I:1 12 標準 Haskell ライブラリーとツール
gfortran V:6, I:62 15 GNU Fortran 95 コンパイラー
fpc I:2 103 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

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

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:80 3116 GNU LALR パーサー生成ソフト
byacc V:0, I:4 258 Berkeley LALR パーサー生成ソフト
btyacc V:0, I:0 243 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 のようなツールは、ソースコード中に見つかる名前のインデックス(とかタグ)を生成することで、人間がコードのレビューするのを助けます。

[ヒント] ヒント

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

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

パッケージ ポプコン サイズ 説明
vim-ale I:0 2591 Vim 8 や NeoVim 用の非同期静的解析エンジン
vim-syntastic I:3 1379 Vim 用のシンタックスシェックのハック
elpa-flycheck V:0, I:1 808 Emacs 用の現代的な同時進行のシンタックスチェック
elpa-relint V:0, I:0 147 Emacs Lisp の正規表現 (regexp) の間違い検出器
cppcheck-gui V:0, I:1 7224 静的 C/C++ コード分析ツール (GUI)
shellcheck V:2, I:13 18987 シェルスクリプトの静的解析(lint)ツール
pyflakes3 V:2, I:15 20 Python 3 の受動チェッカー
pylint V:4, I:20 2018 Python コード静的チェックソフト
perl V:707, I:989 673 静的コードチェックソフト付きのインタープリタ: B::Lint(3perl)
rubocop V:0, I:0 3247 Ruby 静的コード分析ツール
clang-tidy V:2, I:11 21 Clang に基づく C++ の静的解析(lint)ツール
splint V:0, I:2 2320 C プログラムを静的にバグのチェックするためのツール
flawfinder V:0, I:0 205 C/C++ ソースコードを検査してセキュリティーの脆弱性を探すツール
black V:3, I:13 660 非妥協的な Python コードフォーマッター
perltidy V:0, I:4 2493 Perl スクリプトのインデントとリフォーマット
indent V:0, I:7 431 C 言語ソースコード フォーマッター プログラム
astyle V:0, I:2 785 C と C++ と Objective-C と C# と Java のソースコード インデンター
bcpp V:0, I:0 111 C(++) の美化プログラム
xmlindent V:0, I:1 53 XML ストリームリフォーマッタ
global V:0, I:2 1908 ソースコードの検索と閲覧のツール
exuberant-ctags V:2, I:20 341 ソースコード定義のタグファイルのインデックスの構築
universal-ctags V:1, I:11 3386 ソースコード定義のタグファイルのインデックスの構築

12.5. デバグ

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

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

パッケージ ポプコン サイズ 文書
gdb V:14, I:96 11637 gdb-doc が提供する "info gdb"
ddd V:0, I:7 4105 ddd-doc が提供する "info ddd"

12.5.1. 基本的な gdb 実行

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

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

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

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:248, I:567 12053 mtrace(1): glibc 中の malloc デバッグ機能
valgrind V:6, I:37 78191 メモリーデバッガとプロファイラ
electric-fence V:0, I:3 73 malloc(3) デバッガ
libdmalloc5 V:0, I:2 390 メモリーアロケーションのデバグ用ライブラリー
duma V:0, I:0 296 C および C++ プログラムのバッファオーバーランとアンダーランを検出するライブラリー
leaktracer V:0, I:1 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:151, I:555 1592 make-doc が提供する "info make"
autoconf V:31, I:230 2025 autoconf-doc が提供する "info autoconf
automake V:30, I:228 1837 automake1.10-doc が提供する "info automake"
libtool V:25, I:212 1213 libtool-doc が提供する "info libtool"
cmake V:17, I:115 36607 クロスプラットフォームでオープンソースの make システム cmake(1)
ninja-build V:6, I:41 428 Make と似た精神の小さなビルドシステム ninja(1)
meson V:3, I:22 3759 ninja の上に構築された高生産性のビルドシステム meson(1)
xutils-dev V:0, I:9 1484 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 InterfaceThe Apache Software FoundationJavaScript を参照下さい。

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

12.8. ソースコード変換

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

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

パッケージ ポプコン サイズ キーワード 説明
perl V:707, I:989 673 AWK→PERL AWK から PERL へのソースコード変換シフト: a2p(1)
f2c V:0, I:3 442 FORTRAN→C FORTRAN 77 から C/C++ へのソースコード変換ソフト: f2c(1)
intel2gas V:0, I:0 178 intel→gas NASM (Intel フォーマット) から GNU Assembler (GAS) への変換ソフト

12.9. Debian パッケージ作成

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

debmakedh-makedh-make-perl 等のパッケージングを補助するパッケージがあります。



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