作業用スクリプトをPrologで書く2
前回の記事を書いてから知見が溜まり、テンプレートのようなものができたので、ここに載せておきます。
テンプレート
#!/bin/sh swipl
% get_command_line_arguments/1
% Arguments (string list) : 実行時の引数のリスト
get_command_line_arguments(Arguments) :-
current_prolog_flag(argv, Arguments).
% run_command/1
% Command (string) : 実行するコマンド
run_command(Command) :-
shell(Command, _).
% run_command/2
% Command (string) : 実行するコマンド
% ExitCode (int) : 終了コード
run_command(Command, ExitCode) :-
shell(Command, ExitCode).
is_empty_string(String) :-
atom_length(String, 0).
% run_command_with_output/2
% 外部コマンドを実行する(実行結果も取得)
run_command_with_output(Command, Output) :-
run_command_with_output(Command, Output, ErrorOutput),
(is_empty_string(ErrorOutput) -> true ; writeln(ErrorOutput)).
% run_command_with_output/3
run_command_with_output(Command, Output, ErrorOutput) :-
process_create(
path(sh),
[ '-c', Command ],
[ stdout(pipe(Out)), stderr(pipe(Err)) ]
),
(
read_string(Out, _, Output),
% Out, Errの両方のStreamをクローズするとエラーになる
% close(Out),
read_string(Err, _, ErrorOutput),
close(Err)
).
% help/0
help :-
format('NAME\n', []),
format('\t~w - ~w\n', ['mycmd', 'コマンドの説明']),
format('DESCRIPTION\n', []),
format('\t~w ~w\n', ['-l', '[directory]']),
format('\t\t ~w\n', ['ls コマンドを実行する']),
format('\t~w \t ~w\n', ['-p', 'pwd コマンドを実行する']).
% concat_strings/2
concat_strings([], Result) :- Result = ''.
concat_strings([X], Result) :- Result = X.
concat_strings([X1, X2 | Rest], Result) :-
atom_concat(X1, X2, ConcatResult),
concat_strings([ConcatResult | Rest], Result).
% do_command/1
do_command('-l') :- run_command('ls'), !.
do_command('-p') :- run_command('pwd'), !.
do_command(Arg) :-
atom_concat('invalid argument/1 : ', Arg, Msg),
writeln(Msg),
writeln(''),
help.
% do_command/2
% オプションに加えて引数がある場合はこちらに書く
do_command('-l', Dir) :-
concat_strings(['ls ', Dir], Cmd),
run_command(Cmd), !.
do_command(Arg1, Arg2) :-
concat_strings(['invalid argument/2 : ', Arg1, ' ', Arg2], Msg),
writeln(Msg),
writeln(''),
help.
% do_command/0
do_command() :-
writeln('no arguments.'),
writeln(''),
help.
% start/0
% メイン処理 (VSCodeのデバッグのために名称はstartにしておく)
start :-
get_command_line_arguments(Arguments),
length(Arguments, Length),
(
Length =:= 0 -> do_command()
;
Length =:= 1 ->
[Arg1] = Arguments,
do_command(Arg1)
;
Length =:= 2 ->
[Arg1, Arg2] = Arguments,
do_command(Arg1, Arg2)
).
% メインを実行
:- initialization(start, main).
簡単な説明
設計方針は以下のようにしました。
- 引数の数に応じて
do_command
を呼び分ける - それぞれの
do_command
で各引数に対応する実際の処理を書く -
do_command
の引数の数が同じ述語をまとめて書く- ※ まとめて書いておかないとワーニングが表示されるため
- どのオプションにも該当しない場合はエラーメッセージを表示する
- どこでエラーになったかわかりにくいので、
do_command
の引数の数に応じてそれぞれ書いておく
- どこでエラーになったかわかりにくいので、
まとめ
上記のテンプレートで大体のケースは対応できるかと思います。
今回のテンプレートにはありませんが、可変長引数を利用する場合は単純に引数をリストにして書けばよいので、そこまで複雑にはならないかと思います。
複数のオプションで可変長引数を利用するケースなどは、対応がめんどくさくなると思うので、スクリプトの設計方針を変更したほうが良いかもしれません。