Tcl言語の使い方FAQ

訳者まえがき

これは,joe@morton.rain.com (Joe Moss) 氏によって管理される "FAQ: comp.lang.tcl Tcl Language Usage Questions And Answers" の 9月1日版の日本語訳です.この記事のヘッダを以下に示します.
Newsgroups: comp.lang.tcl,comp.answers,news.answers
From: joe@morton.rain.com (Joe Moss)
Subject: FAQ: comp.lang.tcl Tcl Language Usage Questions And Answers
Message-ID: 
Summary: Answers to common questions regarding the use of the 
	Tcl language and building the Tcl interpreter
Keywords: FAQ Tcl TclX
Sender: joe@morton.rain.com (Joe Moss)
Supersedes: 
Reply-To: tcl-faq@morton.rain.com (Tcl Q&A FAQ Maintainer)
Organization: Morton & Associates
Date: Fri, 1 Sep 1995 06:24:39 GMT
Expires: Fri, 6 Oct 1995 06:24:35 GMT

Posted-By: auto-faq 3.1.1.2
Archive-name: tcl-faq/usage
Posting-Frequency: monthly

この日本語訳に対するご意見・ご質問・誤りのご指摘などは,訳者まで. また,その他の詳細な情報については,tcl-faq-j/README.J を参照して下さい.


はじめに

本 FAQ ファイルは,Tcl プログラミング言語に関する, 共通的によく聞かれる質問を扱うことを目的としています. このような質問が本文章で整理されることにより, comp.lang.tcl の同じ投稿の繰り返しが減り, より情報的になることを期待します. また,本文書や他の FAQ によって, より多くの人々が Tcl や Tcl ベースのアプリケーションを より便利に使えるようになることも期待します.

このFAQは, Joe Moss (joe@morton.rain.com) により管理され,毎月 comp.lang.tcl と news.answers に投稿され,Tclのアーカイブである ftp.aud.alcatel.com (198.64.191.10) の /tcl/docs ディレクトリに送られます.また,WWWでは http://route.psg.com/tcl.html で読むことが出来るでしょう.

本 FAQ は Tcl に関して扱うものであり, Tk についてのものではないことに注意して下さい. Tk ツールキットに関しては, Thomas J. Accardo (tja@cpu.com) により投稿されているFAQ (後述の質問: "Tclの情報をもっと知るには?" の中にある その他の Tcl FAQを参照のこと). しかしながら,必然的に情報がだぶる場合もあります. Tk ウィジェットの扱い方に関する最もよく聞かれる質問のいくつかは, Tcl インタプリタの動作自体に対する誤解に起因するものです. こういった問題は,今後もここで扱われます. また,Tcl の扱いの幾つかの例題では, Tkのウィジェットを実行する必要がある場合もあるでしょう.

Tk に依存しない拡張(拡張 Tcl や Expect など,ただし,TkX や expecTk, BLT は除く)の使い方に関する質問と回答もまた,ここで取り扱われます.

この文章のソースは,現在では HTML 形式で管理するようになりました. テキストバージョンに質問番号や質問目次, From や Subject 行を魔法の様に自動的に付加するために,2 つのスクリプト (もちろん,Tcl で書かれています)を通しています. WWW から参照できるバージョンは,変更がある度に更新されますが, ASCII テキストバージョンは月に1度,生成,投稿されるだけです.

最終変更: Sat Sep 16 00:59:21 PDT 1995


一般情報:

Q.A1- Tclの情報をもっと知るには?

ニュースグループ comp.lang.tclは, Tcl言語とそれをベースとするパッケージに関して議論を行うために存在します. Tclに関する各種FAQドキュメントは, ftp.aud.alcatel.com のディレクトリ /tcl/docs から入手可能で,そこには出版されたものやオンライン上で入手可能な 各種情報とそのポインタが記載されています. これらは,World Wide Web でも, http://www.smartpages.com/bngfaqs/comp/lang/tcl/top.htmlからアクセス可能です.

>Webで情報サーフィンできるなら,以下の3つがスタート点としてよいでしょう.
http://www.sco.com/Technology/tcl/Tcl.html
http://web.cs.ualberta.ca/~wade/HyperTcl/
http://www.sunlabs.com/research/tcl/

また,本文書の末尾にある"外部URLリスト" から手繰る手もあります.

もちろん,配付品のソースコードそれ自体にも, 多くの重要なドキュメントが含まれています. 最新の正式バージョンは 7.4 で,早期α試験バージョンである 7.5a1版も入手可能です.これらは, ftp://ftp.smli.com/pub/tcl/ から得られます.

Q.A2- 私のマシン上で Tcl を動かすために必要な情報を得るには?

Tcl の配布物の中から"porting.notes" というファイル名のファイルを探して見ましょう. これには,これまで多くの人々から寄せられた, 様々なマシンや OS 上で Tcl を動かす際の注釈事項がまとめられています. また,コードに触ったりする前にまず最初に読むべき, "README"というファイルもあります (これは,どんなパッケージにも言えることですけど).

また, Sun Tcl/Tk Web pages から,移植問題関連のデータベースにアクセスできます.

さらに,本文章に後述する 様々なプラットホーム上でインタプリタを構築するには には,まだその殆どがTcl7.3以前のバージョンに関するものではありますが, そうした情報を提供しています.

Q.A3- TclとCを組み合わせるには?

TclはCコードと組み合わせて使用する事を想定されています. このために,これら2つを組み合わせる様々な方法が提供されています. 以下に,それらのうちの幾つかを示します (詳細はマン・ページを参照して下さい):

最後の2つは,その他のコマンドと同レベルの機能を提供するものではありません. が,(C側の)ソースコードをアクセスする事ができない場合には,必要となるでしょう.

これらの機能のより詳細を解説したドキュメントがあります.WWWで, http://psg.com/~joem/CmdWrite.htmlから読むことが出来る拡張Tclの配付に含まれる TclCommandWritingmanページを参照して下さい.

また, part two of Larry's Tcl FAQTcl Bibliography には他の参考文献が掲載されています.

加えるに,標準Tclや他の拡張を組み込んだTclでは expectの 仮想ttyドライバ経由による制御,Xイベントやファイルイベント,タイマ, Tk4.x のアイドル・コールバックハンドラによる方法もあるでしょう. また, ftp://ftp.vnet.net/pub/users/drh/ から入手できるEmbedded Tkも見ておいた方が良いでしょう.

Q.A4- 欲しい機能全てを提供できるように,数々の拡張を組み合わせるには?

簡単な答えとしては,Tcl_AppInitを, 欲しい拡張の初期化プロシージャ全てを呼び出すように改造することです. これはTclソース配付品から,tclAppInit.c(Tkの場合は,tkAppInit.c)をコピーし, 変更した上で,使用しているTclライブラリに追加する事で行えます. 例えば,標準のTcl_AppInitは,以下の形式を取っています:

    int
    Tcl_AppInit(interp)
        Tcl_Interp *interp; /* Interpreter for application. */
    {
        /* ... */

        if (Tcl_Init(interp) == TCL_ERROR) {
            return TCL_ERROR;
        }

        /* ... */
    }
「foo拡張」を追加するには, 次のようにfooの初期化関数の呼び出しを追加してやります:
    int
    Tcl_AppInit(interp)
        Tcl_Interp *interp; /* Interpreter for application. */
    {
        /* ... */

        if (Tcl_Init(interp) == TCL_ERROR) {
            return TCL_ERROR;
        }

        if (Foo_Init(interp) == TCL_ERROR) {
            return TCL_ERROR;
        }

        /* ... */
    }

より詳細な情報は,Tcl_AppInitのマンページや, tclAppInit.cファイル自体を参照すると良いでしょう.

(前述の)単純な回答はすべての場合に動作するものではない,というのが, より完全な回答です.多くの拡張は,より複雑な改造と, その拡張の使用に依存した組み込み方を必要とします.

拡張Tclを組み込みたい場合,Tcl_AppInitは, その配付に含まれるものを用いねばなりません. またプログラムをリンクする際には,標準Tclライブラリより前に拡張Tclの ライブラリをリンクしなければなりません.例えば:

    cc -o mytclsh mytclXAppInit.c -ltclx -ltcl ...

あるいは,Tkを組み込んだインタプリタでは:

    cc -o mywish mytkXAppInit.c -ltkx -ltk -ltclx -ltcl ...

幸いなことに, Make-A-Wishのような,複数の拡張を1つに組み込む為のパッケージもあります. また,他のポピュラーな拡張と一緒に組み込む為の設定ファイルを同時に配付して いる拡張もあります.例えば,Sven Delmas によって書かれた幾つかの拡張は, 指定された拡張を組み込んだインタプリタを構築するMakefileを生成するための, configureのオプションを指定することができます. これらの入手先は part four of Larry Virden's FAQ を参照のこと.

Q.A5- 動的ローディングって何ですか?

動的ローディングに関する議論は,しばしばcomp.lang.tclで見掛けられます. そして,さまざまな拡張の組み込みを 動的ローディングによって提供しようとするその願いは正しいものです. が,標準Tcl配付品では現在その機能はサポートされていません.

しかしながら,やがてリリースされる 7.5 版では組み込まれる予定であり, 現在入手可能の試験バージョンでは組み込み済であります.ですから,拡張を ロード可能モジュール形式に変換し始めることはできるでしょう.

Q.A6- Tclがインストールしてない環境でも動作する,スタンドアローン・プログラムを作るには?

Earle Lowe (lowee@cpsc.ucalgary.ca) は,次のように答えてます:

TCL/Tk が存在していてもいなくても関係なくどこででも動作できる スタンドアローンのプログラムを作りたいならば, もう少し作業する必要があります.

基本的には,これにはまず, TCL初期化ファイルをC文字列に変換してやることからはじめます. そして,Tcl_Init()Tk_Init()を呼ぶ代わりに, 変換された C 文字列を引数にTcl_Eval()を呼んでやります.

Alexei Rodriguez (alexei@cis.ufl.edu) が作成した wish_compiler パッケージを ftp://ftp.aud.alcatel.com/tcl/code/wish_compiler.shar.gz から入手すると良いでしょう.

このパッケージにはtcl2cコンバータが入っていて, その使用法の説明もついて来ます.

Makefile のちょっとした技を使えば, TCL/Tk の目的通りの使用 (様々な種類の wish を用いた,インタプリタ型言語としての使用)法で用い, コードを実行させる時には, コンパイルされたスタンドアローンプログラムを生成する様にすることも できるでしょう.

[訳注:]
訳者が理解しているところでは,TkWWW が,この技を使っています.

他の選択肢として,「組み込みTk」を利用することも可能です. これは, ftp://ftp.vnet.net/pub/users/drh/ から得られます.

Q.A7- tclshがインストールされている場所に関係なく,スクリプトを動作させるには?

幾つかの技が知られていますが,もっとも一般的なのは, Tclではコマンド行も含めてバックスラッシュ{\}が継続行の印として使える, という機能を利用したものです.例えば:

    #! /usr/local/bin/tclsh

    puts "Hello World"

と書く替わりに,

    #! /bin/sh
    # 次の行は/bin/shでは評価されるが, Tclではされない \
      exec tclsh $0 ${1+"$@"}

    puts "Hello World"

と書いてやることで,実際にtclshが何処にインストールされているかに関係なく (ユーザの環境変数PATHにインストールされたパスが含まれている必要はありますが), 動作させることができます.

Q.A8- 何で<ほげほげ拡張>がデフォルトの配付に含まれてないの?

しばしば,ある言語拡張が(彼らにとっては必要不可欠であるにもかかわらず) なぜ,コア言語に統合されていないのかと聞かれます.

実に多くの人々が様々なシステムの上で, 非常に幅広い用途のためにTclを使用しているのだ,ということを考えに 入れねばなりません. また,Tclは元もと, プログラマがその作成するアプリケーションに必要なスクリプト言語をいちいち 作成する手間を削減するために設計された, 最低限のプログラミングの構成と枠組を提供する 組み込み用の言語であることも忘れてはなりません.

とは言うものの,コアTcl言語はここ数年で大きく発展して来ました. 追加された機能のうちの幾つかは, 他で開発された拡張から組み込まれたものです. 拡張によって提供される機能がTclの全てのユーザにとって嬉しいと思われた場合, JohnはコアTclにそれを組み込んで来ました. 例えば,連想リストやファイル入出力コマンド(と,ファイルハンドラ), unknownプロシージャは全て,標準Tclに組み込まれる以前は 拡張Tcl によって提供されていたものです. Tk 4.0 では,他の人々の手によってTcl7.3/Tk3.6用に開発された addinputphoto widget 拡張と同等の機能を含んでいます.


プログラミング関連の質問と回答:

Q.B1- 連想リストや属性リストを生成/使用するには?

配列や,拡張Tclのキー付きリストを使うと良いでしょう.

例えば,以下のようにプログラムして:

    keylset ttyFields ttyName tty1a
    keylset ttyFields baudRate 57600
    keylset ttyFields parity strip

echo $ttyFieldsを実行すると,以下を得ます:

    {ttyName tty1a} {baudRate 57600} {parity strip}

あるいは配列を使って:

    set ttyFields(ttyName)  tty1a
    set ttyFields(baudRate) 57600
    set ttyFields(parity)   strip

Q.B2- Tclで乱数を生成させるには?

拡張Tclには,システムの標準Cライブラリを使って乱数発生する randomというコマンドがあります.

例えば,0から9までの乱数を生成するならば,以下のようにします:

    set random_number [random 10]

また,srandomというコマンドで乱数生成器の種を設定できます.

乱数の種を設定するには,以下のコマンドかその組合せを使用すると 良いでしょう.(ただし,Unix系のシステム):

    [pid]
    [file atime /dev/kmem]
    [getclock]        (拡張Tclのみ)

すべてTclで書かれた, 幾つかの仮想乱数生成関数がcomp.lang.tclに投稿されています. それらの一覧は, tcl-faq/part4 を参照して下さい.

以下はその1つで,拡張Tcl版と同じシンタックスでTclのみで書かれたものです. 定数はDon Libesによるものです.引数の正当性のちょっとしたチェックに 注目のこと.

    proc random {args} {
        global RNG_seed
    
        set max 259200
        set argcnt [llength $args]
        if { $argcnt < 1 || $argcnt > 2 } {
            error "wrong # args: random limit | seed ?seedval?"
        }
        if ![string compare [lindex $args 0] seed] {
            if { $argcnt == 2 } {
                set RNG_seed [lindex $args 1]
            } else {
                set RNG_seed [expr \
                    ([pid]+[file atime /dev/kmem])%$max]
            }
            return
        }
        if ![info exists RNG_seed] {
            set RNG_seed [expr ([pid]+[file atime /dev/kmem])%$max]
        }
        set RNG_seed [expr ($RNG_seed*7141+54773) % $max]
        return [expr int(double($RNG_seed)*[lindex $args 0]/$max)]
    }

Q.B3- あるプロシージャから返される複数のパラメータを,別のプロシージャに引数として渡すには?

y は複数の引数を要求し,x は複数の語を返すとします. Tcl の evalコマンドを使ってやります:

    eval y [x]

Q.B4- 配列をプロシージャに渡すには?

もし可能であるならば,グローバル変数を使うよりも, upvarコマンドを試して見るべきです. もし関数がイベント駆動であるならば,グローバル変数を使わざるを得ませんが.

    # 配列の要素を出力する
    proc show_array arrayName {
        upvar $arrayName myArray

        foreach element [array names myArray] {
           puts stdout "${arrayName}($element) =  $myArray($element)"
        }
    }

    set arval(0) zero
    set arval(1) one
    show_array arval

上に示したように,プロシージャから配列を返させるには, 配列名を引数として与えてやるだけです. 配列に対して行ったどのような変更操作も,親(呼出元)の配列に反映されます.

拡張 Tcl は,キーと値の組合せのリストである,キー付きリストと呼ばれる コンセプトを導入していて,ネットワーク経由などでも,値をルーチンに引き 渡すことが出来ます.

Q.B5- 外部コマンドを実行して,その出力をパイプを通して読むには?

例えば,幾つかのファイルに対して grep でパターンマッチを行うならば, 以下の方法などが考えられます:

Karl Lehenbauer (karl@NeoSoft.com) が書くところによれば:

    set files [glob /home/cole/stats/*]

    proc parseInfo { site } {
       global files

    #
    # 変数siteは予めlistboxで設定されている
    #
       set in [open [concat "|/usr/bin/grep $site $files"] r]

       while {[gets $in line]>-1} {
          puts stderr $line
       }
       catch {close $in}
    }

問題点: マッチした文字列が,ディレクトリ順に戻らない

もしリターン・コードをチェックし,かつ,そのコマンドの出力を使いたいならば: Kevin B. Kenny (kennykb@dssv01.crd.ge.com) が書くところによれば:

    if [catch {exec ls} data] {
        # execがエラーを起こすならば, $errorCodeに終了ステータスが入る
    } else {
        # execは成功
    }
    # どんな場合でも,`data' には子プロセスの出力全てが格納される.

Karl Lehenbauer (karl@NeoSoft.com) が errorCode を,文字列 "CHILDSTATUS", 子プロセスのプロセスID, 子プロセスの終了ステータスの 3 つの要素を持つリストとして加えた点に注意して下さい.

Q.B6- スクリプト中でプロシージャの定義を削除するには?

プロシージャ名を無名(空文字列)にリネームします.例えば:

    rename procedureName ""

Q.B7- 7桁以上の倍精度の精度を得るには?

Tcl 7.x では,グローバル変数tcl_precisionを1〜17の範囲で setします.例えば:

    % expr 4*atan(1)
    3.14159
    % set tcl_precision 0
    can't set "tcl_precision": improper value for precision
    % set tcl_precision 3
    3
    % expr 4*atan(1)
    3.14
    % set tcl_precision 16
    16
    % expr 4*atan(1)
    3.141592653589793
    % set tcl_precision 18
    can't set "tcl_precision": improper value for precision

Tcl 6.xでは,tclExpr.cモジュール中で%gの代わりに %lf を使うように書換えます.

Q.B8- 非組み込み呼出の発生をトラップするには?

見付けられないコマンドが呼び出された際には, プロシージャunknownが, (見付けられなかった)コマンド名とその引数を引数にして自動的に呼び出されます. 実際の所,Tclや拡張Tclは,この機能を, コマンドやライブラリの自動読み込みの機能を実現するのに使用しています. また,"tclsh"や"tcl"シェルを対話的に用いる場合, 外部コマンドの実行を(最初に"exec"と打ち込まなくとも)可能に しています.

従って,unknownプロシージャを書き換えれば, 独自の機能拡張を行えます. 望むならば,自動読み込みの機能を削除することさえも可能です.

Q.B9- 環境変数の読み出しや設定を行うには?

例えば,次のように行います.

    set olddisplay $env(DISPLAY)
    set env(DISPLAY) unix:0

回答を与えてくれた Joel Fine (joel@cs.berkeley.edu) に感謝.

環境変数が存在しているかどうかを調べるのは,次のようにします:

    if [info exists env(VARNAME)] {
        # 環境変数が定義されているので,それを使う.
        set value $env(VARNAME)
    } else {
        # 環境変数が設定されていないのでデフォルト値を使う.
        set value "the default value"
    }

Q.B10- 頭に0が付いた数字を扱うには?

Tclでは、頭に0がついた数字は8進数(あるいは16進数. ただし,0の直後に文字'x'がついた場合)として評価されます. 時としてこれは便利な機能ですが,いくつかの問題点もあります.

Q.B11- コマンドライン引数を参照したいのですけど?

プログラム名はグローバル変数 argv0 に代入され,その引数はグローバル変数 argvに リスト形式で格納されます.変数 argc には, argvのリストの要素数が 納められます.例を示すならば:

    #! /usr/local/bin/tclsh

    if { $argc != 2 } {
        puts stderr "$argv0: Usage: $argv0 <infile> <outfile>"
        exit 1
    }

    set infile  [lindex $argv 0]
    set outfile [lindex $argv 1]

Q.B12- コメントの扱いにバグを見付けたぞ!

時々,彼らが期待したのと違う (そして,他の言語が同じ状況で振舞う振舞いとも異なる) Tclの振舞を見付ける人がいます.彼らは,この期待外れの振舞いをバグと考えます. こうした状況のもっとも起こり得る場所は,コメント文と思われます.

Tclでは,パーザに通される全ての物は,コメントも含めて, 適切なリスト構造を取っていなければなりません(そして,そう,コメントはパーザを 通るのです.これは,幾つかの言語では単にその前の段階で削除してしまうだけ であるのと対象的です). 一般的にこれは,コメントアウトされた行中の括弧の対応がきちんと取れていなければ ならないことを意味します.

従って,例えばある条件をチェックするif文があったとして, 別の条件をテストさせたいと思ったとしましょう.前の条件をコメントアウトし, 新しいif文を書いたとします. このコードは,おそらくは「対応する閉じ括弧がない」というエラーになるでしょう.

    ## 間違った例
    if { $newflag } {
    # if { $oldflag } {
        puts hello
    }

この場合,括弧の対応を取るようにしなければなりません.例えば:

    ## 正しい例
    if { $newflag } {
    # if { $oldflag } {
        puts hello
    # }
    }

Tclのコメントでこの他に面白いのは,継続行機構が適用される点でしょう. すなわち:

    # これは末尾がバックスラッシュで終るコメント行 \
      そして,この行も上のコメントの一部となる.

Q.B13- バイナリ・データの読み書きをするには?

内部的には,Tclインタプリタはほぼ全ての物をnull末端の文字列として 蓄えています.これはバイナリデータな文字列(より的確に言うならば, nullが途中に組み込まれた文字列)として直接には扱えないことを意味します.

しかしながら,幾つかの操作はデータがTcl変数に格納されていなくとも 動作させることができます.例えば,ファイルハンドルは外部プログラムに 直接張り付けられます.

    set infp [open "|compress -dc $fileName"]

    exec gzip -c $newFileName <@ $infp

あるいは,拡張Tclならば:

    set infp [open "|compress -dc $fileName"]
    set outfp [open "|gzip -c $newFileName" w]

    copyfile $infp $outfp

あるいは,何らかの方法でTclで利用可能な形式にデータを変換してしまう 手もあるでしょう:

本節を寄贈してくれた Wayne Throop throop@aur.alcatel.com に感謝.

Q.B14- シグナルをトラップするとか,その他のUnix固有の機能を使うには?

拡張Tclは,たくさんのこの手の機能を提供しています. 例えば,拡張Tcl には signal コマンドがあります.

signal action siglist [command]

ここでactionは, "default", "ignore", "error", "trap", "get"に加えて POSIXの"block"と "unblock"アクションのいずれか (もちろん,POSIX システムでのみ有効). siglistは,記号表現,あるいは数値表現のUnixシグナルのリスト (前置記法 SIG はオプション). commandは,定義するエラー・ハンドラ (あるいは,単に,{puts stdout "そんなキーは押さないで!"} :-). trap は期待通り動きますし,errorget は, キーボード・トラバーサルが必要な会話的プログラムでは,とても有効です.

拡張 Tcl は,forkなどの機能も持っています.

回答は Brad Morrison (brad@NeoSoft.com).

Q.B15- 間接指定をしたいのだけど.-- 何で$$varが動かないの?

インタプリタで評価される度毎に, たかだか1レベルのみの置換だけが可能なのです. また,変数置換の発生時には,インタプリタはドル記号を見つけると,それに続く 不正な文字に至るまで全ての文字列(ここで不正とは,英数字,及び下線記号以外の 文字として定義される)を変数名として扱います.従って,配列記法や ${varname}形式では区切りとして認識されます.

$$varの場合, ドル記号の後の最初の文字が不正文字(次のドル記号)であるので, 変数名が存在しない事になり,変数置換は働きません (2番目のドル記号はそのまま残ります). そして残りのドル記号のための構文解析が開始され,その右側が変数名として 扱われます.このため,直ちに2番目のドル記号以降が変数置換され, 構文解析はそれ以降の置換がないかどうかを調べにいってしまいます. それ以降何もないならば,インタプリタを通したこのパスによる置換は 終了します(1回のみ行われることに注意).

evalコマンドはその引数をインタプリタに通します. そこで,2回目のパスを インタプリタに通すためにevalを使う事ができ, そうすれば$$varを動かす事ができるでしょう:

   % set a 5
   5
   % set var a
   a
   % puts $$var              # 動かない
   $a
   % eval puts $$var         # 動く.--- けど危険.
   5

ですが,変数varの内容に特殊文字 (スペースやセミコロンなど)が含まれていると,問題が発生してしまうでしょう.

よりよい方法は,1引数のみ与えられた場合のset コマンドの振舞を応用し, コマンド置換を変数置換に組み合わせて使用するのです:

   % puts [set $var]         # 安全に動作.
   5
また,実際にはコマンド置換(一度に1組の[]にしか働かない)だけを 使ってもできます:
   % puts [set [set var]]    # 同じように動作
   5

似たような問題として,変数var1, var2, と var3の値の出力を考えて見ましょう:

    set var1 3.14159
    set var2 hello
    set var3 13
    foreach num {1 2 3} {
	puts "var$num = [set var$num]"
    }

は,以下を出力します:

    var1 = 3.14159
    var2 = hello
    var3 = 13

upvar コマンドは異なった変数にも使えます.

加えるに,7.4版からは置換を行わせるのに使える subst コマンドが装備されています.

上記にあげた全ての方法は配列に対しても使えます.

Q.B16- クォートされた文字列を意図した通りに動かすには?

この問題点を扱った長文の記事は, ftp://ftp.aud.alcatel.com/tcl/docs/README.programmer.gzで得られます.

ここに掲載するのは,短い答えです:

Q. 後になって実行するコマンドを組み立てようとしてるのですが, 空白や特殊文字を値に含む変数で問題が出てしまいます.

A. コマンドを組み立てる最も安全な方法は, list コマンドを使うことです. そうすれば,リスト構造を壊さずにすみます. 余計なエバリュエータによる評価が行われてしまうこともあるので, ダブルクォートの使用は避けましょう. 例題として, クリックしたら表示しているラベルを出力するボタンを生成するプログラムを 示します.

間違えた答え #1:
button $myname -text $label -command "puts stdout $label"
なぜ?
それは,もし$labelが空白文字を含むなら, putsコマンドに間違えた数の引数が渡されてしまうからです. $labelが$や[]文字を含むなら, 出力されずに評価されてしまいます.

良い答え #2:
button $myname -text $label \
       -command [list puts stdout $label]
なぜ?
list$labelの値を適切にクォートするからです.
Q. 後になって実行するコマンドを組み立てようとしてるのですが, 変数が組み立て時に評価されてしまったり, コマンドの実行時に評価されてしまったりして,思い通りに動いてくれません.

A. これを行う最も明晰な方法は, 実行時の変数の使用を隠蔽するようにプロシージャを定義することです. そして,前に述べた様に listコマンドを用いてプロシージャの呼び出しを組み立てあげます. (実行中であってもプロシージャの定義は可能です. 別のプロシージャの内部で生成されても, それはグローバルなスコープを持ちます.)

間違えた答え #1:
button $myname -text $label -command \
       [list puts stdout $ArrayOfDynamicStuff($label)]
なぜ?
配列変数はボタンが生成された時の値で置き換えられてしまい, ボタンがクリックされた時の値とはならないからです. また,コマンドがグローバルなスコープで実行されることにも 注意しなければなりません. そのため, "global ArrayOfDynamicStuff"を コマンドの中に含める必要はありません.

間違えた答え #2 (バッククォートと list):
button $myname -text $label -command \
       [list puts stdout \$ArrayOfDynamicStuff($label)]
なぜ?
これでは,listコマンドと $ のバッククォートが, 互いに喧嘩してしまいます.結果として全体の振舞いは以下の様になり,
puts stdout {$ArrayOfDynamicStuff(foo)}
配列要素の値に対する置換が働かなくなってしまいます.

いかがわしい答え #3 (バッククォートとダブルクォート):
button $myname -text $label -command \
       "puts stdout \$ArrayOfDynamicStuff($label)"
なぜ?
これは$labelの値が特殊文字や空白文字を含まない時だけ, まともに動きます.

完全な答え #4 (proc):
       proc doit { i } {
          global ArrayOfDynamicStuff
          puts stdout $ArrayOfDynamicStuff($i)
       }
       button $myname -text $label -command [list doit $label]
なぜ?
ボタンのために簡単なTclプロシージャを作ってやる様にしておけば, ちゃんと動作してくれます. 何故なら,凝ったクォーティングをする必要をなくしてくれますし, ボタンから起動される処理の仕様変更も,容易になります.
Q. プロシージャに可変個数の引数を渡そうとしてるのですが, $argsを正しく展開するところで,つまってしまいます.

A. evalとダブルクォートを組み合わせて使おうとするのは, 止めましょう. なぜなら,そうすると,余計な評価が起動されてしまうからです. evalコマンドは,その引数が1つ以上の場合は,それらを1つに連結します. そのため,ダブルクォートで1つに括ってやる必要が,まったく無くなります. 前述のbuttonの例を拡張して見ましょう.

間違えた答え #1:
       proc mybutton { myname label args } {
          button $myname -text $label \
              -command [list puts stdout $label] $args
       }
なぜ?
変数$argsに格納されている mybuttonへの全ての追加引数は, 1つのリスト要素にグループ化されてしまっています. しかしながら,buttonコマンドはその引数に, サブリストではなく個々の引数を要求するからです.

間違えた答え #2:
       proc mybutton { myname label args } {
          eval "button $myname -text $label \
                  -command [list puts stdout $label] $args"
       }
なぜ?
ダブルクォートは,$argsと同じように, $labelの展開を可能にします. そのため,$labelの値に空白文字が含まれるなら, buttonコマンドには, 正しくない引数並びが渡されることになってしまいます.

良い答え #3:
       proc mybutton { myname label args } {
          set cmd {button $myname -text $label \
                   -command [list puts stdout $label]}
          eval $cmd $args
       }
なぜ?
evalは,最初に 2 つの引数を連結して, 次にその結果をインタプリタを介して実行します. これは,$cmd$argsから, 外側のカリー括弧を外して, 2つの変数の全ての要素を1つのリストにすると考えられます. $labelは,1回しか評価されません. そして,putコマンドも正しく動きます. さらに,argsが入れられる度にも,1回しか処理しません.
Q. なんで if/while/for文で 文法エラーになってしまうのでしょう.

A. ひょっとして,

    wish: set foo bar
    wish: if {$foo == bar} {puts stdout bar}
    syntax error in expression "$foo == bar"
の様に書いてはいないでしょうか.

これでは,文字列としても変数としても評価されません. 式中のオペランドとしての文字列は, ダブルクォートか中括弧で括ってやらねばなりません.

    wish: if {$foo == "bar"} {puts stdout bar}
    wish: if {$foo == {bar}} {puts stdout bar}
の様に書き換えましょう.

どちらを選ぶかは,展開させたいかどうかで決めて下さい.

この記述は,Jesper Blommaskog (d9jesper@dtek.chalmers.se)からのコントリビュートです.

Q.B17- プロシージャ定義を複数のスクリプトで共有するには?

明示的にファイルをsourceするのではなく, Tclライブラリを作るようにしましょう.
Step 1. ファイルを共有ディレクトリに置きます.
Step 2. "ライブラリ" のための,tclIndex を作ります.
私は Makefile 中に,次のように書いています.
       install.index:
           (cd ${DESTDIR}/tclscripts/lib; \
           echo 'source /usr/local/lib/tcl/init.tcl;\
	   auto_mkindex . *.tk' | tcl ; exit 0)
       
Step 3. 作成した Tcl スクリプトに, ライブラリを参照させるようにします.
例えば,以下のようにします.
       # local additions
       lappend auto_path /usr/local/lib/tcl_local \
            $env(RDS_TCL_SCRIPTS)/lib
       
このようにすれば, スクリプトはプロシージャをライブラリの中から参照しようとし, "unknown"コマンドが 自動的にプロシージャを読み込んでくれます.
この記述は, Joe VanAndel (vanandel@ncar.ucar.edu)からのコントリビュートです.

Q.B18- リストに挿入された要素を得るには?

よく,なぜ
  linsert $list 0 ..
と実行したのに,結果に .. が挿入されていないのかと聞かれます.

Jesper Blommaskog (d9jesper@dtek.chalmers.se) が, 以下のように答えています.

lappend以外のリスト操作では,戻り値を保存しなければなりません. これは,list,lindex,lrange,lreplaceに 当てはまります.
これは,ひょっとしたらやりたかった事が正しく動く例です.
  set list [ linsert $list 0 .. ]

Q.B19- ファイル識別子から非ブロック化入力を行うには?

From Frank Smith (frank@arraysystems.nstn.ns.ca) によると, 拡張 Tcl を持っているなら,

  read $fileId [fstat $fileId size]
のようにして実現可能だそうです. これは,fileIdからその時点で読み込める最大のバイト数を読み込み, 結果としてブロック化しません.

Q.B20- デッドロックさせずにパイプで入出力するには?

標準入出力パッケージは, (入出力する)文字をバッファリングして 不必要なシステムコールを行わなくて済むようにすることで, 処理速度を最適化しています. これは会話的な使用には不適切ですので, 標準入出力はその振舞いをファイルが端末である場合には変更します. (ですが)パイプに書き込む時には,端末に書き込んでいる訳ではないので, 書き込んだ出力はバッファリングされてしまいます. 同様に,パイプの向こうにある他のプログラムがその応答を出力する時にも, バッファリングされてしまいます. 結果として,パイプの両端のプログラムは, 互いに入力を待って立往生してしまいます. これを解決するには,Tcl ではflushコマンド, C ではfflush()を使ってパイプをフラッシュすることです. もっとも, しばしば,パイプラインの端のプログラムの制御が出来ない場合もあります. その様な場合は,解決する唯一の解は,仮想端末(pty)を使う事です. が,これはそう簡単に行えることではありません.

これは標準の Tcl ではサポートされていませんが, expectでは, spawnコマンドで ptyを開いてコマンドを起動する機能をサポートしています.

Q.B21- 実行中のバージョンを知るには?

だれも答えてくれる人はいない様ですが,大丈夫. Tcl 自身が,そのバージョンを答えてくれます. 以下のように打ち込んで見ましょう.

  info tclversion
また,Tk のバージョンは,以下のようにして知ることが出来ます.
  puts $tk_version
その他の拡張では,他の手段があるでしょう(例えば拡張Tclでは, infox versionでバージョン情報が得られます).

7.4版からは,tclshの実行ファイルはデフォルトで版番号をつけてインストール されるようになりました.これは,度のバージョンで走らせているのかを 分かりやすくしてくれます.しかしながら,こうした形でインストールされていない システムでは,バージョンをシェルプロンプト上で知るには以下のようにします:

    echo 'puts $tcl_version;exit 0' | tclsh

(;exit 0の部分はwishの特性からwishでのみ必要とされます.が, これが必要でない場合でも何もしないから大丈夫です.)

Q.B22- 配列名を入れた変数を使うには?

Eric Bleeker (ericbl@paramount.nikhefk.nikhef.nl) が書くには:

ひょっとしたら,こんな風に書いていませんか:

    % set foo "bar baz"
    bar baz
    % foreach aap $foo {
        set $aap(1) "something"
    }
    can't read "aap(1)": variable isn't array

これは,Tclが配列要素"aap(1)"を, 存在してもいないのに置換しようとしているのです. これを解決するには,以下のようにします.

    % foreach aap $foo {
        set [set aap](1) "something"
    }

この場合は,2 つの配列"bar"と "baz"は生成されます.

別の形式として,以下のような形も考えられます.

    % foreach aap $foo {
        set ${aap}(1) "something"
    }

エラーメッセージに関して

本章では幾つかのエラーとそのありうる(でも表面上は分からない)理由を 示します.

Q.C1- "not found" または "Command not found"

このエラーメッセージはTclからではなく,シェルから報告されるものです. スクリプトがおそらく #! で始まっていて,その後にTclインタプリタの パス名が記述されているのでしょうが,これが使用しているシステムにとって 長過ぎるのです.多くのUnixシステムでは,(#!を含めて)32文字にこの 長さは制限されています.従って,下記のような例はエラーとなるでしょう.

    #! /usr/home/homedir/very/long/path/tclsh

    # rest of script

tclshの実行ファイルを別のディレクトリに移動するなり,シンボリックリ ンクを張るなりすれば,このパス長を短くすることができます.また,別解と してパスを明示的に指定しないという手も使えます. これを行う実例は,質問 「tclshがインストールされている場所に関係なく, スクリプトを動作させるには?」を参照のこと.

[訳注]
無論,書かれてるパスにtclshの実行ファイルが存在してない,なんて のは論外であることは言うまでもありません.

Q.C2- invalid command name "}"

開き丸括弧で終る行をコメントアウトしてるのでしょう.詳細な情報は,質問 「コメントの扱いにバグを見付けたぞ!」 を参照してください.

Q.C3- missing close-brace

丸括弧の対応が取れてないのでしょう.一見して分からないような状況に ある場合は, 「コメントの扱いにバグを見付けたぞ!」 を参照すると良いでしょう.

Q.C4- X server insecure (xauth形式の認証を使おう!)

Tkは,sendコマンドを使うためには,安全なXサーバを使うことを要求し ます.サーバを安全にするには,Thomas AccardoのTk ツールキット使い方FAQ の質問2.A.7「Tk3.3をちゃんと起動するには - セキュリティエラーメッセー ジが出るのですが?」や, http://ce-toolkit.crd.ge.com/tkxauth/を参照すると良いでしょう.

Q.C5- expected integer but got ...

このエラーは,何らかの算術演算操作を行っている際に,それが整数を要 求する演算であるにもかかわらず非整数を与えた場合に発生します.やっかい なことに,値が実は浮動点小数や単なるキャラクタ文字列であっても,それが 正当な整数に読めるならば,このエラーは発生しません.頭に0がつく数字の 扱い方は,質問頭に0が付いた数字を扱うには? を参照してください.

Q.C6- "Undefined symbol: main" あるいは,その類似エラー

アプリケーションをコンパイル/リンクする際に,main関数 が見付からないというエラーが発生する場合,おそらくは,アプリケーション がTcl7.3以前用に書かれているにもかかわらず,それを7.4版以降にリンクし ようとしているのでしょう.

Tclの昔のバージョンは main関数をライブラリ (libtcl.a)に持っていました.これは,特にC++プログラムで, さまざまな問題を引き起こすことになりました.そのため,Tcl7.4b1の開始時 にこれは削除されました.Tclライブラリをリンクするアプリケーションは, 現在ではmain関数を自前で定義する必要があります.


プラットホーム固有,あるいは移植性に関する質問:

本セクションは,Tcl配布品を様々なプラットホーム上でコンパイルするための 幾つかのヒントを掲載します.ここに掲載された内容は,最新のリリースでは 恐らくは当てはまらないでしょう.

Tcl 7.4以降のリリースをコンパイルするのに,標準配布品の構築で問題を 抱えて問い合わせをしようとする人々のために,Sunによって管理される オンラインデータベースが利用可能になりました. 自動的にコンパイルが終らない場合は, http://www.sunlabs.com/research/tcl/ の使用しているプラットホームに関する情報を覗いて見てください. 一方,まだ情報が登録されていないプラットホームにおいて インストールに成功したならば,その実験結果を是非とも公表してやってください.

Q.A2- 私のマシン上で Tcl を動かすために必要な情報を得るには?も参照のこと.

Q.D1- AIXでTcl/Tkの共有ライブラリを構築するには?

Dov Grobgeld (dov@menora.weizmann.ac.il)が, AIX 3.1.5で,TclとTkの共有ライブラリを作る方法を発表しています.:

Tclでは:

    cc -o tkshar.o *.o -bE:tclshar.exp -bM:SRE -berok -lX11 -lm
    ar r libtclshr tclshar.o

Tkでは:

    cc -o tkshar.o *.o -bE:tkshar.exp -bM:SRE -berok -Ltcl -lX11 -lm -ltclshr
    ar r libtkshr tkshar.o

ここで tckshar.exp と tkshar.exp は,外部関数のリストです.

AIX と IBM のフォントの問題もあります. X サーバを修正するパッチをIBMから入手する必要があり, そうすれば,フォント関連が動きます.

Q.D2- HP-UXでTclをコンパイルできた人はいませんか?

ポーティング ノート(ファイルporting.notes)を参照して下さい. また,Configureが互換バージョンのopendir()を (コンパイルに)使うべきだと判断してしまうけど, dirent.hが存在していないという問題の報告が幾つかなされています. これは,globでエラーを引き起こします. TclXのreaddirでも同様です.

tcl 7.3では,opendirの互換ファイルを 削除する変更を行いましょう. そして,-DNO_DIRENT, -DUSE_DIRENT2を削除すれば,問題は解決します.

Q.D3- VMSでTclをコンパイルできた人はいませんか?

1993年5月4日に, John Kimball (jkimball@src.honeywell.com) から寄せられた情報によれば, 彼はTcl 6.7とTk 3.2をVMS 5.5 上にポーティングすることに成功したそうです. ファイルの情報はカタログを参照して下さい.

Q.D4- SCO UnixでTclをコンパイルするには,どうしたらいいんでしょう?

"#undef select"をtkEvent.cに加え, main.cの460行目あたりのTK_EXCEPTIONの参照を削除します.

Tkは,そのウィジェット立体表示のために, 表示枠のカラー確保のための独自の仕組みを使っています. これがTkを,"Pseudo Color" ディスプレイ・クラスで, 16セルのカラーマップのマシン上で実行する際に問題となります.

8bitプレーンを使えないならば, ディスプレイ・クラスを "StaticColor" にする様に, サーバを "-static"(Xsco) や "-analog"(Xsight)オプション指定で立ち上げるとよいでしょう. これは,カラーマップを完全に読み込みのみとし, 要求された色にできるだけ最も近い色を返すようにします.

この情報は, Keith Amann (Keith_Amann@stortek.com)から寄せられました.

Q.D5- tclTextを走らせると,formatやscanでエラーになるのは何故?

この(scanf/printfの)問題は, 多くのシステムで見られます.これに関しては,あまり神経質になる必要はありません --- `より高度な'機能が使えないだけです. もしあなたがCのハッカーならば,同じ問題に出喰わす筈です.

例えば,(こうしたシステムでは)printf("%Ng", double_value)strtod("+",&terminal) は,誤った答えを返すでしょう.

Q.D6- tclTextをIrix 4.0.1で走らせると一杯エラーが出るのは何故?

これは,4.0.1のオプティマイザのバグで, 4.0.2では修正されています. tclVar.cをオプション-O0(最適化無し)で コンパイルして下さい.

Q.D7- CrayでTclを問題なく走らせた人はいませんか?

ポーティング・ノートの変更点に関する記述を参照して下さい.また, Booker C. Bense (benseb@grumpy.sdsc.edu)は, バージョン3.0.1.6ではキャラクタ型のポインタの深刻な問題があり, Tclがクラッシュすると報告しています. バージョン3.0.2.1を使うと多少はマシですが, formatコマンドの些細なバグと, scanコマンドの深刻な問題は残るそうです

Q.D8- Tcl 7.1/Tk 3.4をNeXTSTEP 3.1で走らせるには?

この問題に関しては,以前,大きな議論の元となりました.以前の記述を, この分野のオーソリティと思える方からの解説に置き換えることとします.

Robert Nicholson (robert@steffi.demon.co.uk)によれば:

これはTCL7.1 と TK3.4 を対象とします.

これは Thomas Funke の報告の改造です.

TCL7.1をNeXTにインストール

tcl7.1をNeXTSTEP 3.1にインストールするには,以下のようにします:

この時,NeXTライブラリlibsys_a.aで供給されるものに対して, strtodtmpnamを区別するために, 何か適当な名前に変更したいと思うでしょう. これを行うためには,AC_FLAGSに以下の行を付け加えることで出来ます.

    -Dstrtod=newstrtod -Dtmpnam=newtmpnam

名称変更された関数の定義は,compat/*.cにあります.

全てが正しく動くかどうかをチェックするには, tclshを起動し,以下を実行します.

    % expr {"0" == "+"} 
    0

間違えたstrtod関数を用いていたならば,返り値は1になり, 深刻なエラーとなります.精度に関するエラーは無視します.

TclをNextにインストールするには?

Tk3.4 の中にもstrtod関数の呼び出しがあるので, 前述の../tcl-7.1/compat/strtod.oにリンクを張り, AC_FLAGSに-Dstrtod=newstrtodを付け加える必要があります.

注意: Tk の raise 試験は, ウィンドウマネージャにtvtwmを使っていると失敗します. この試験を通すためには,twmやfvwmなどに変更する必要があります.

Q.D9- SGI Indigo上でエラーが出るのはなぜ?

Gordon Lack (gml4410@ggr.co.uk)によれば, SGIのCコンパイラには変数引数の扱いにいくつかのエラーがあるそうです.

tclVar.cは,IRIX C4.0.1では,変数に関するバグのため, 引数-O0でコンパイルされねばなりません.

Makefile のルール指定を以下のように書き換えてやります.

    # GGR SG needs -O0 for varargs at 4.0.1

    CC_SWITCHES0 =  -O0 -I. -I${SRC_DIR} ${AC_FLAGS} ${MATH_FLAGS} \
          ${GENERIC_FLAGS} ${PROTO_FLAGS} ${MEM_DEBUG_FLAGS} \
          -DTCL_LIBRARY=\"${TCL_LIBRARY}\"

    tclVar.o: tclVar.c
          $(CC) -c $(CC_SWITCHES0) $<

Peter Neelin (neelin@pet.mni.mcgill.ca)は,次のような注意をしてます.

私は,ファイルConfig.mkを次のように変更して, TclX 7.3aをSGI(irix4.0.5)でコンパイルしました.

    71c71
    < CFLAGS=-cckr -D__GNU_LIBRARY__
    ---
    > #CFLAGS=
    106,107c106,107
    < TCL_PLUS_BUILD=TCL_PLUS
    < CCPLUS=g++
    ---
    > #TCL_PLUS_BUILD=TCL_PLUS
    > CCPLUS=CC
    191,193c191
    < MAN_DIR_SEPARATOR=
    < 
    < LIBOBJS=strftime.o
    ---
    > #MAN_DIR_SEPARATOR=.

-D__GNU_LIBRARYが,srandom問題を解決しますが, 思うに,-cckrwaitpidで プロトタイプ・エラーを起こします(汚いですが,とりあえず動きます). strftimeがなぜ必要だったかは,忘れてしまいました.

私の作成した Config.mkのバージョンが欲しいならば,メールして下さい.

Q.D10- expectをSolaris2.3で構築するには?

Jeff Abramson (jra@hrcms.jazz.att.com)が語るには:

SunPro cc 2.0.1を使ってもgcc 2.5.8を使っても問題はありません. SunProならば,次のようにします.

    CC=cc ./configure --prefix=directory_of_your_choice
    make CC=cc

gccならば,以下の通りです.

    CC="gcc -fwritable-strings" ./configure \
        --prefix=directory_of_your_choice
    make CC="gcc -fwritable-strings"

Q.D11- SequentにTcl/Tkを移植するには?

Andrew Swan (aswan@soda.berkeley.edu)によって解明されました.

TclとTk双方で,非ANSIのSequent ccではなく,gccを用います.

Tclでは,数学ライブラリに,2つの問題があります.第1に,Sequentの数学ラ イブラリが,'fmod'関数を含んでいないことです.

私はfmodのソースをftp.uu.netから入手し,サブディレクト リcompatに入れました.そして,それをMakefileに追加しました. fmodは,'isnan'と'finite'関数を 内部で使用していますが,tclが無限とnanをサポートするとは信じなかったの で,それらはコメントアウトしただけです.

数学ライブラリのもう1つの問題は,そのライブラリ中に' tanh'関数のコピーが2つ,含まれてしまっていることです. "ar"を使ってライブラリをオブジェクトに分解し, 再度組み立て直して,tanhのコピーを取り去ってしまえば,簡 単にfixできます.

この様な変更を行えば,Tclのコンパイルはきれいに終了し,試験も, scanの試験以外はすべて通ります.どうも,Dynixでは, *scanf関数が壊れてる様です.この問題は,8進数の終りで確認 できます.私はちょうど,この問題に取り掛かったところです.解決策は, *scanf関数のソースを見付けだし,それを使うことになるでしょ う.

Tkをコンパイルするために,私はwchar_tのようなものを定義した,新しい バージョンのstddef.hを作らねばなりませんでした.私はそれを別のインクルー ド・ディレクトリにコピーし,そのディレクトリを最初に指定して,それが参 照されるようにしました.Tkでも数学ライブラリの問題 (tanh) が発生します.これら以外もありますが,すべてのコンパイルはきれいに終了 し,すべての試験にもパスします.

Q.D12- AU/X 3.0でシンボルテーブルが一杯になるのを避けるには?

AU/X 上でのソースのコンパイルが終ったならば, デフォルトのテーブルのアロケーションを拡張する -A {factor}引数をリンカに指定しなければなりません. {factor}は,デフォルトのアロケーションの何倍にするかを指定します. -A 2を試してみましょう.この移植に関するこれ以上の詳細は, Walter B. Kulecz (wkulecz@medics.jsc.nasa.gov)博士に 連絡すると良いでしょう.

Q.D13- MS-DOSやMacOS,その他の非UnixマシンでTclをコンパイルするには?

関連するツールや移植の完了品,この話題に関するメーリングリスト情報 は,Larry VirdenのFAQ ( tcl-faq/part1) や ( tcl-faq/part4)を参照のこと.

Q.D14- QNXマシン上でTclをコンパイルするには?

Steve Furr (furr@qnx.com)が示す所によれば:

QNX 上で Tcl を動かすためにしたことをまとめるならば,


End of FAQ