Tcl – 外部プログラムの実行について -exec-

外部プログラムの実行

Tclシェルを起動して対話的に実行する場合、未定義のTclコマンドが入力されると、外部プログラムとして実行するように設定されています。

[使用例1]

$ tclsh

# 外部コマンドのdateを実行
% date
2019年  2月 13日 水曜日 15:36:02 JST

# 予期せぬ動作
% set d [date]
2019年  2月 13日 水曜日 15:36:35 JST

% puts $d

%

1つ目の例は、OS標準コマンドのdateコマンドを実行して現在の日時を表示しています。

2つ目の例は、dateコマンドを実行して現在の日時を変数dに格納しようとして失敗しています。

Tclシェルを起動して通常のシェルと同じようにOS標準コマンドが実行できるのは便利であるのですが、色々試していると、2つ目の例のように予期せぬ振る舞いに遭遇することもあります。

外部プログラムの実行をさせないようにするには、変数auto_noexecを定義します。

[使用例2]

$ tclsh
% set auto_noexec 1
1
% date
invalid command name "date"

execコマンド

[書式]

exec ?switches? arg ?arg ...? ?&?

execコマンドはサブプロセスを呼び出します。

Tclのスクリプトファイル内で外部プログラムを実行したり、実行した結果を利用するには、execコマンドを使います。Tclシェルを起動して対話的に実行する場合にも明示的にexecコマンドを実行することで、外部コマンドの出力結果を利用することができます。

[使用例1]

$ tclsh
% set d [exec date]
2019年  2月 13日 水曜日 16:45:10 JST
% puts $d
2019年  2月 13日 水曜日 16:45:10 JST

execコマンドはパイプラインやリダイレクトもサポートしています。以下の例はlsコマンドの出力結果をパイプラインを使ってgrepコマンドに渡しています。

$ tclsh

% set dir [exec ls | grep hello*]
hello-w.tcl
hello.tcl

% puts $dir
hello-w.tcl
hello.tcl

execコマンドのオプションや使用できるリダイレクションは以下のようなものがあります。

スイッチ意味
-ignorestderr 標準エラーチャネルへのメッセージ出力をエラーケースとして処理しないようにする。

※ 2>@stderr と同じ?
サブプロセスのstderrをメインプロセスのstderrに戻す模様。

[参考]
https://wiki.tcl-lang.org/page/exec
https://wiki.tcl-lang.org/page/open
-keepnewline パイプラインの出力における末尾の改行を削除しません。
--スイッチ(?switches?)の終わりを示します。これに続く引数は「-」で始まっていても最初の引数(arg)として扱われます。
リダイレクション意味
|個々のコマンドを区切ります。「|」の前に記述したコマンドの標準出力は、次のコマンドの標準入力に渡されます。
※「|」記号はパイプ、パイプラインと読みます。
|&個々のコマンドを区切ります。「|&」の前に記述したコマンドの標準出力と標準エラーの両方が、次のコマンドの標準入力に渡されます。この形式のリダイレクションは、2>や>&などの形式をオーバライドします。
< fileNamefileNameで指定したファイルを開いて最初のコマンドの標準入力に渡します。
<@ fileIdfileIdで指定したファイル識別子から最初のコマンドの標準入力に渡します。fileIdはopenコマンドなどにより、事前に読み込み可能なモードで開かれていなければなりません。
<< valuevalueで指定した値を最初のコマンドの標準入力に渡します。
> fileName 最後のコマンドの標準出力をfileNameで指定したファイルに出力します。ファイルの内容は上書きされます。
2> fileName全てのコマンドからの標準エラーをfileNameで指定したファイルに出力します。ファイルの内容は上書きされます。
>& fileName 最後のコマンドの標準出力と全てのコマンドからの標準エラーの両方をfileNameで指定したファイルに出力します。ファイルの内容は上書きされます。
>> fileName最後のコマンドの標準出力をfileNameで指定したファイルに出力します。ファイルの内容は追加されます。
2>> fileName全てのコマンドからの標準エラーをfileNameで指定したファイルに出力します。ファイルの内容は追加されます。
>>& fileName最後のコマンドの標準出力と全てのコマンドからの標準エラーの両方をfileNameで指定したファイルに出力します。ファイルの内容は追加されます。
>@ fileId最後のコマンドの標準出力をfileIdで指定したファイル識別子の出力チャネルに出力します。fileIdはopenコマンドなどにより、事前に書き込み可能なモードで開かれていなければなりません。
2>@ fileId全てのコマンドからの標準エラーをfileIdで指定したファイル識別子の出力チャネルに出力します。fileIdはopenコマンドなどにより、事前に書き込み可能なモードで開かれていなければなりません。
2>@1 全てのコマンドからの標準エラーをコマンドの実行結果にリダイレクトします。この演算子はコマンドパイプラインの最後でのみ有効です。
>&@ fileId最後のコマンドの標準出力と全てのコマンドからの標準エラーの両方をfileIdで指定したファイル識別子の出力チャネルに出力します。fileIdはopenコマンドなどにより、事前に書き込み可能なモードで開かれていなければなりません。
&最後の引数が「&」の場合、パイプラインはバックグランドで実行されます。この場合、execコマンドは、パイプライン内の全てのサブプロセスのプロセス識別子をリストにしたもの返します。

パイプラインの最後のコマンドからの標準出力は、リダイレクトされていない場合はアプリケーションの標準出力に送られ、パイプラインのすべてのコマンドからのエラー出力はリダイレクトされない限りアプリケーションの標準エラーに送られます。

標準出力がリダイレクトされていない場合、“ 2> @ 1”が指定されていない限り、execコマンドはパイプラインの最後のコマンドから標準出力を返します。この場合、標準エラーも含まれます。

もし、パイプライン内のいずれかのコマンドが異常終了したり、強制終了されたり中断されたりした場合、execはエラーを返します。エラーメッセージにはパイプラインの出力とそれに続く異常終了を説明するエラーメッセージが含まれます。

グローバル変数のerrorcodeには、最後に発生した異常終了に関する追加情報が含まれます。

もし、いずれかのコマンドが標準エラーに出力して、その標準エラーがリダイレクトされずに、-ignorestderrも指定されていない場合にはexecはエラーを返します。 エラーメッセージには、パイプラインの標準出力と、それに続いて異常終了に関するメッセージ(もしあれば)を出力し、その後に標準エラーが出力されます。

execコマンドの使用例

execコマンドの使用例をいくつか紹介します。

UNIXでの使用例

[使用例1]

set n [exec cat str.txt | wc -l]
puts $n
=> 8

使用例1は、catコマンドのファイル指定で読み込んで、wcコマンドで行数の取得。結果を変数nに代入しています。

[使用例2]

(1) exec cat < str.txt | wc -l > str.line
(2) set fid [open str.line r]
    set line [exec cat <@ $fid]
    => 8

(1)は、ファイル名で指定したファイルの内容を標準入力からcatコマンドに渡して、wcコマンドで行数の取得。結果をファイル:str.lineへ出力しています。

(2)は、str.lineを開いてファイル識別子を指定してcatコマンドに渡しています。

[使用例3]

(1) exec date > ping.txt
(2) set pid [exec ping -i 5 192.168.11.1 >>& ping.txt &]
(3) exec kill $pid
    exit
 
    $ cat ping.txt
    2019年  2月 21日 木曜日 16:00:48 JST
    PING 192.168.11.1 (192.168.11.1) 56(84) bytes of data.
    64 bytes from 192.168.11.1: icmp_seq=2 ttl=64 time=0.678 ms
    64 bytes from 192.168.11.1: icmp_seq=3 ttl=64 time=0.743 ms
    64 bytes from 192.168.11.1: icmp_seq=4 ttl=64 time=0.662 ms
    64 bytes from 192.168.11.1: icmp_seq=5 ttl=64 time=0.751 ms

(1)は、日付をping.txtに書き込みしています。

(2)は、pingコマンドをバックグランドで起動し、標準出力と標準エラーの両方をping.txtに追加書き込みするようにしています。

ping -i 5 は、相手先に5秒間隔でパケットを送信します。

末尾の「&」がないとフォアグランドで子プロセス(pingコマンド)を起動するので、起動した子プロセスが終了するまで、Tcl(親プロセス側)で次のコマンドが実行できなくなります。その場合、Ctrl+Cなどでpingコマンドを強制終了する必要があります。

(2)のようにバックグランドで実行した場合、execコマンドの戻り値は、起動したpingコマンドのプロセスIDを返します。

(3)は、pingコマンドを終了させています。

Windowsでの使用例

[使用例1]

(1) exec notepad &
(2) exec c:/windows/system32/notepad.exe &
(3) exec {*}[auto_execok notepad] &

(1)(2)(3)は、いずれもメモ帳を起動します。

(1)は、notepad.exeがPATH環境変数内にあることを想定しています。

(2)は、プログラムへのパスを明示的に指定しています。

(3)は、auto_execokコマンドを使用している。

  auto_execok cmd

auto_execokはTclライブラリのコマンドで、指定したコマンド(cmd)が実行可能ファイル(シェル組み込み含む)として存在するかどうか判別します。存在している場合は、execコマンドで実行可能ファイルを実行する為に引数リストをexecコマンドに渡します。存在しない場合は、空文字列を返します。

{*}の構文は、複数の引数に展開します。Tclはデフォルトで複数の引数に展開しないので、複数の引数が1つの文字列として扱われ、エラーになる可能性があります。{*}は8.5から使えるようになりました。それまでは、evalコマンドで以下のように記述していました。

  eval exec [auto_execok notepad] &

Windowsの場合、拡張子にプログラムの関連付けがされているので以下のような使い方もできます。

[使用例2]

(1) exec {*}[auto_execok start] https://wiki.tcl-lang.org
(2) exec {*}[auto_execok start] hello-w.tcl &

(1)は、ブラウザを起動してTcler’s Wiki!のページを表示します。

(2)は、hello-w.tclのプログラムを起動します。

{*}やevalコマンドを使わなかった場合、以下のエラーになります。

couldn't execute "C:\Windows\System32\cmd.exe \c start": no such file or directory

evalコマンドと{*}構文は「Tcl – evalコマンドと{*}構文について」で紹介しています。

コメント

  1. しがないTcler より:

    Tcl/Tkは情報が少なく,いつもありがたく参考にさせていただいてます。

    外部プログラムの実行[使用例1]の「# 予期せぬ動作」のところ,
    こちらのブラウザでは閲覧した日付(2022/05/19)が表示されています。

    # 予期せぬ動作
    % set d 2022/04/19 <– 実際は [date] でしょうか。
    2019年 2月 13日 水曜日 15:36:35 JST

    • いっしー より:

      > 実際は [date] でしょうか。

      はいそうです。
      連絡ありがとうございます。修正しました。

      ワ-ドプレスにショートコードという機能があるのですが、使用しているテーマ(Cocoon)のほうで、dateを[]で囲むと現在の日時を表示するショートコードを追加していたようです。

      https://wp-cocoon.com/date-shortcode/

タイトルとURLをコピーしました