第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.1.8. zenity を使うシェルスクリプト例
12.2. Make
12.3. C
12.3.1. 単純な C プログラム (gcc)
12.4. デバグ
12.4.1. 基本的な gdb 実行
12.4.2. Debian パッケージのデバグ
12.4.3. バックトレースの収集
12.4.4. 上級 gdb コマンド
12.4.5. X エラーのデバグ
12.4.6. ライブラリーへの依存の確認
12.4.7. メモリーリーク検出ツール
12.4.8. 静的コード分析ツール
12.4.9. バイナリーのディスアッセンブリー
12.5. Flex — 改良版 Lex
12.6. Bison — 改良版 Yacc
12.7. Autoconf
12.7.1. プログラムをコンパイルとインストール
12.7.2. プログラムのアンインストール
12.8. 究極の短い Perl スクリプト
12.9. ウェッブ
12.10. ソースコード変換
12.11. Debian パッケージ作成

パッケージされたソースコードを追跡するのに十分な範囲で Debian システム上でプログラムを学ぶ人への指針を示します。次はプログラムの関して特記すべきパッケージと対応する文書パッケージです。

表12.1 プログラムをすることを補助するパッケージのリスト

パッケージ ポプコン サイズ 文書
autoconf V:29, I:226 1868 autoconf-doc が提供する "info autoconf
automake V:27, I:220 1707 automake1.10-doc が提供する "info automake"
bash V:853, I:999 5799 bash-doc が提供する "info bash"
bison V:10, I:113 2061 bison-doc が提供する "info bison"
cpp V:394, I:806 41 cpp-doc が提供する "info cpp"
ddd V:1, I:13 3965 ddd-doc が提供する "info ddd"
exuberant-ctags V:7, I:38 333 exuberant-ctags(1)
flex V:10, I:101 1174 flex-doc が提供する "info flex"
gawk V:355, I:478 2199 gawk-doc が提供する "info gawk"
gcc V:148, I:606 43 gcc-doc が提供する "info gcc"
gdb V:21, I:140 7983 gdb-doc が提供する "info gdb"
gettext V:53, I:367 7076 gettext-doc が提供する "info gettext"
gfortran V:20, I:63 16 gfortran-doc が提供する "info gfortran" (Fortran 95)
fpc I:4 113 python-doc が提供する python(1) と html ページ (Pascal)
glade V:1, I:12 2209 メニューが提供するヘルプ (UI Builder)
libc6 V:933, I:999 10679 glibc-docglibc-doc-reference が提供する "info libc"
make V:154, I:622 1211 make-doc が提供する "info make"
xutils-dev V:2, I:18 1466 imake(1), xmkmf(1), 他
mawk V:371, I:997 183 mawk(1)
perl V:610, I:996 651 perl-docperl-doc-html が提供する perl(1) と html
python V:683, I:988 648 python-doc が提供する python(1) と html ページ
tcl8.4 V:3, I:50 NOT_FOUND tcl8.4-doc が提供する tcl(3) と詳細なマンページ
tk8.4 V:1, I:31 NOT_FOUND tk8.4-doc が提供する tk(3) と詳細なマンページ
ruby V:103, I:321 38 ri が提供する ruby(1) と詳細なマンページ
vim V:118, I:393 2374 vim-doc が提供するヘルプ (F1) メニュー
susv2 I:0 15 "The Single UNIX Specifications v2" を取得
susv3 I:0 15 "The Single UNIX Specifications v3" を取得

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

[警告] 警告

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

[注意] 注意

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

[ヒント] ヒント

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

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

#!/bin/sh
 ... コマンド行

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

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

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

多くのシステムスクリプトは POSIX シェル (表1.13「シェルプログラムのリスト」参照下さい) のどれで解釈されるか分かりません。システムのデフォールトシェルは実際のプログラムをさしているシムリンクである "/bin/sh" です。

  • bash(1)lenny 以前の場合

  • dash(1)squeeze 以降の場合

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


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

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

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

[注記] 注記

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

[ヒント] ヒント

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

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


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


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

  • ":" 付き = 存在非ヌル文字列をテストするオペレータ

  • ":" 無し = 存在のみをテストするオペレータ


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

  • 成功: 0 ("真")

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

[注記] 注記

シェル条件文の文脈中の "0" は "真" を意味します、一方 C 条件文の文脈中の "0" は "偽" を意味します。

[注記] 注記

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

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

  • "<command> && <成功したらこのcommandも実行> || true"

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

  • 次のようなマルチラインスクリプト断片

if [ <条件式> ]; then
 <成功ならこのコマンドを実行>
else
 <成功でばいならこのコマンドを実行>
fi

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



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

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

  • シェルは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" オプションで起動すると、シェルは実行するコマンドを全てプリントするようになります。これはデバグをするのに非常に便利です。

dvdisaster(1) によって RS02 データーを補足した ISO イメージを生成する簡単なスクリプトの例を次に示します。

#!/bin/sh -e
# gmkrs02 : Copyright (C) 2007 Osamu Aoki <osamu@debian.org>, Public Domain
#set -x
error_exit()
{
  echo "$1" >&2
  exit 1
}
# Initialize variables
DATA_ISO="$HOME/Desktop/iso-$$.img"
LABEL=$(date +%Y%m%d-%H%M%S-%Z)
if [ $# != 0 ] && [ -d "$1" ]; then
  DATA_SRC="$1"
else
  # Select directory for creating ISO image from folder on desktop
  DATA_SRC=$(zenity --file-selection --directory  \
    --title="Select the directory tree root to create ISO image") \
    || error_exit "Exit on directory selection"
fi
# Check size of archive
xterm -T "Check size $DATA_SRC" -e du -s $DATA_SRC/*
SIZE=$(($(du -s $DATA_SRC | awk '{print $1}')/1024))
if [ $SIZE -le 520 ] ; then
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is good for CD backup:\\n $SIZE MB"
elif [ $SIZE -le 3500 ]; then
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is good for DVD backup :\\n $SIZE MB"
else
  zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
    --text="The data size is too big to backup : $SIZE MB"
  error_exit "The data size is too big to backup :\\n $SIZE MB"
fi
# only xterm is sure to have working -e option
# Create raw ISO image
rm -f "$DATA_ISO" || true
xterm -T "genisoimage $DATA_ISO" \
  -e genisoimage -r -J -V "$LABEL" -o "$DATA_ISO" "$DATA_SRC"
# Create RS02 supplemental redundancy
xterm -T "dvdisaster $DATA_ISO" -e  dvdisaster -i "$DATA_ISO" -mRS02 -c
zenity --info --title="Dvdisaster RS02" --width 640  --height 400 \
  --text="ISO/RS02 data ($SIZE MB) \\n created at: $DATA_ISO"
# EOF

デスクトップに "/usr/local/bin/gmkrs02 %d" のようなコマンド設定をしたローンチャを作るのも面白いかもしれません。

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

ルールファイルのシンタックスは次です。

ターゲット: [ 前提 ... ]
 [TAB]  command1
 [TAB]  -command2 # エラー無視
 [TAB]  @command3 # エコー抑制

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

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

%.o: %.c header.h

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



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

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)

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

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

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

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

gdb の良い入門書は "info 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) コマンドは省略できます。タブ展開はシェル同様に機能します。

Debian システムではデフォールトではインストールされたバイナリーはストリップされているべきなので、通常のパッケージではほとんどのデバグシンボルが削除されています。gdb(1) を使って Debian パッケージをデバグするには、対応する *-dbg パッケージをインストールする必要があります (例えば libc6 の場合 libc6-dbg)。

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

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

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

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

$ dch -i

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

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

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

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

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

自分で作った "main()" と "yywrap()" を供給する必要があります。そうでない場合にはあなたの flex プログラムは次のようでなければライブラリー無しにコンパイル出来ません。これというのは "yywrap" はマクロで、"%option main" とすると "%option noyywrap" が暗示的に有効になるからです。

%option main
%%
.|\n    ECHO ;
%%

上記の代わりにとして、cc(1) のコマンドラインの最後に (ちょうど AT&T-Lex が"-ll" 付きであるように) "-lfl" リンカーオプションを使いコンパイルすることが出来ます。この場合、"%option" は必要なくなります。

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


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

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

%%

%%

Autoconf は自動的にソフトウエアーのソースコードパッケージを GNU のビルドシステムを使って種々様々な Unix 的システムに適応させるためのシェルスクリプトを作成するツールです。

autoconf(1) は"configure" という設定プログラムを作成します。"configure" は"Makefile.in" を雛形として使って自動的にカスタム化した "Makefile" を作成します。

どんな AWK スクリプトでも a2p(1) を使えば自動的に Perl に書き換えられますが、1行 AWK スクリプトから1行 Perl スクリプトへの変換は手動変換するのが最良です。

次の AWK スクリプト断片を考えます。

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 の次の機能を利用しています。

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

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

コマンドラインオプションに関しては perlrun(1) を参照下さい。もっとクレージーな Perl スクリプトに関しては、Perl ゴルフが面白いです。

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

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

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

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

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

    • "http://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 を参照下さい。

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

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


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

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