第12章 プログラミング

目次

12.1. シェルスクリプト
12.1.1. POSIX シェル互換性
12.1.2. シェル変数
12.1.3. シェル条件式
12.1.4. シェルループ
12.1.5. Shell environment variables
12.1.6. シェルコマンドライン処理シーケンス
12.1.7. シェルスクリプトのためのユーティリティープログラム
12.2. Scripting in interpreted languages
12.2.1. Debugging interpreted language codes
12.2.2. GUI program with the shell script
12.2.3. Custom actions for GUI filer
12.2.4. 究極の短い Perl スクリプト
12.3. Coding in compiled languages
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. Dynamic call tracing tools
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 アーカイブを含める必要があるかもしれません。

Please consider to use version control system tools. See 「Git」.

[警告] 警告

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

[注意] 注意

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

[ヒント] ヒント

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

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

#!/bin/sh
 ... command lines

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

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

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

Many system scripts may be interpreted by any one of POSIX shells (see 表1.13「シェルプログラムのリスト」).

  • The default non-interactive POSIX shell "/bin/sh" is a symlink pointing to /usr/bin/dash and used by many system programs.

  • The default interactive POSIX shell is /usr/bin/bash.

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


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

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

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

[注記] 注記

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

[ヒント] ヒント

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

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


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


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

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

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


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

  • 成功: 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" が必要です。



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

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

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

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

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

    • 空白: space tab newline

    • Metacharacters: | ; & ( )

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

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

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

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

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


[ヒント] ヒント

Although moreutils may not exist outside of Debian, it offers interesting small programs. Most notable one is sponge(8) which is quite useful when you wish to overwrite original file.

See 「Unix 的テキスト処理」 for examples.


When you wish to automate a task on Debian, you should script it with an interpreted language first. The guide line for the choice of the interpreted language is:

  • Use dash, if the task is a simple one which combines CLI programs with a shell program.

  • Use python3, if the task isn't a simple one and you are writing it from scratch.

  • Use perl, tcl, ruby, ... if there is an existing code using one of these languages on Debian which needs to be touched up to do the task.

If the resulting code is too slow, you can rewrite only the critical portion for the execution speed in a compiled language and call it from the interpreted language.

The shell script can be improved to create an attractive GUI program. The trick is to use one of so-called dialog programs instead of dull interaction using echo and read commands.


Here is an example of GUI program to demonstrate how easy it is just with a shell script.

This script uses zenity to select a file (default /etc/motd) and display it.

GUI launcher for this script can be created following 「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

This kind of approach to GUI program with the shell script is useful only for simple choice cases. If you are to write any program with complexities, please consider writing it on more capable platform.


Here, 「Flex — 改良版 Lex」 and 「Bison — 改良版 Yacc」 are included to indicate how compiler-like program can be written in C language by compiling higher level description into C language.

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)

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" は必要なくなります。

Lint like tools can help automatic static code analysis.

Indent like tools can help human code reviews by reformatting source codes consistently.

Ctags like tools can help human code reviews by generating an index (or tag) file of names found in source codes.

[ヒント] ヒント

Configuring your favorite editor (emacs or vim) to use asynchronous lint engine plugins helps your code writing. These plugins are getting very powerful by taking advantage of Language Server Protocol. Since they are moving fast, using their upstream code instead of Debian package may be a good option.

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

パッケージ ポプコン サイズ 説明
vim-ale I:0 2591 Asynchronous Lint Engine for Vim 8 and NeoVim
vim-syntastic I:3 1379 Syntax checking hacks for vim
elpa-flycheck V:0, I:1 792 modern on-the-fly syntax checking for Emacs
elpa-relint V:0, I:0 135 Emacs Lisp regexp mistake finder
cppcheck-gui V:0, I:1 6196 静的 C/C++ コード分析ツール (GUI)
shellcheck V:2, I:11 18987 シェルスクリプトのリントツール
pyflakes3 V:1, I:14 24 passive checker of Python 3 programs
pylint V:4, I:17 1976 Python コード静的チェックソフト
perl V:644, I:990 669 静的コードチェックソフト付きのインタープリタ: B::Lint(3perl)
rubocop V:0, I:0 3247 Ruby 静的コード分析ツール
clang-tidy V:1, I:8 21 clang-based C++ linter tool
splint V:0, I:3 2320 C プログラムを静的にバグのチェックするためのツール
flawfinder V:0, I:0 205 C/C++ ソースコードを検査してセキュリティーの脆弱性を探すツール
black V:2, I:8 557 uncompromising Python code formatter
perltidy V:0, I:4 2338 Perl スクリプトのインデントとリフォーマット
indent V:0, I:9 426 C language source code formatting program
astyle V:0, I:3 761 Source code indenter for C, C++, Objective-C, C#, and Java
bcpp V:0, I:0 111 C(++) beautifier
xmlindent V:0, I:1 53 XML ストリームリフォーマッタ
global V:1, I:2 1896 ソースコードの検索と閲覧のツール
exuberant-ctags V:3, I:25 345 build tag file indexes of source code definitions

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


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) コマンドは省略できます。タブ展開はシェル同様に機能します。

Since all installed binaries should be stripped on the Debian system by default, most debugging symbols are removed in the normal package. In order to debug Debian packages with gdb(1), *-dbgsym packages need to be installed (e.g. coreutils-dbgsym in the case of coreutils). The source packages generate *-dbgsym packages automatically along with normal binary packages and those debug packages are placed separately in debian-debug archive. Please refer to articles on Debian Wiki for more information.

デバッグしようとしているパッケージに*-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" が使われているようにします。

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

The backtrace can be obtained by gdb(1) using one of the following approaches:

For infinite loop or frozen keyboard situation, you can force to crash the program by pressing Ctrl-\ or Ctrl-C or executing “kill -ABRT PID”. (See 「プロセスの停止」)

[ヒント] ヒント

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

 $ MALLOC_CHECK_=2 gdb hello

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



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

Autotools is a suite of programming tools designed to assist in making source code packages portable to many Unix-like systems.

  • Autoconf is a tool to produce a shell script "configure" from "configure.ac".

    • "configure" is used later to produce "Makefile" from "Makefile.in" template.

  • Automake is a tool to produce "Makefile.in" from "Makefile.am".

  • Libtool is a shell script to address the software portability problem when compiling shared libraries from source code.

The software build system has been evolving:

  • Autotools on the top of Make has been the de facto standard for the portable build infrastructure since 1990s. This is extremely slow.

  • CMake initially released in 2000 improved speed significantly but was still build on the top of inherently slow Make.

  • Ninja initially released in 2012 is meant to replace Make for the further improved build speed but is also designed to have its input files generated by a higher-level build system.

  • Meson initially released in 2013 is the new popular and fast higher-level build system which uses Ninja as its backend.

See documents found at "The Meson Build system" and "The Ninja build system".

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

  • 質問 (クエリー) はブラウザーのユーザーに 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 等のパッケージングを補助するパッケージがあります。