めも:Rust の練習帳
リポジトリ
環境
macOS 組み込みの Bash(/bin/bash
)、ではなくミラーリポジトリのマイナーバージョンまで合致している Bash をインストールした。
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin23)
Copyright (C) 2007 Free Software Foundation, Inc.
$ /opt/homebrew/bin/bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
参考
- bash リポジトリのミラー
- https://github.com/bminor/bash
- C 製なんだね
後で調べる
-
#[derive(Debug)]
などの属性ってどういう仕組み? - Rust のテストランナーの挙動
-
#[test]
ってどういう仕組みだっけ?テスト対象の関数をどうやって見つけて(テストディスカバリ)どうやって実行してるんだっけ? - テストの並列化はどうやってやるの?
-
-
which
コマンドで、ビルトインコマンドと外部コマンドを引数に与えた時の挙動の違いはどうな
ビルトインコマンドと外部コマンド
by ChatGPT
ビルトインコマンド以外の、brew
(macOSのHomebrew)などの外部コマンドのことは一般的に「外部コマンド」または「ユーティリティ」と呼びます。
ビルトインコマンドと外部コマンドの違い
-
ビルトインコマンド:
- シェル(例:Bash、Zsh、Fishなど)自体に組み込まれているコマンドです。
- 例:
cd
,echo
,alias
,export
,history
など。
-
外部コマンド:
- シェルに組み込まれていない、外部のプログラムやスクリプトとして実行されるコマンドです。
- シェルが実行ファイルを検索して見つけ、子プロセスとして実行します。
- 例:
brew
,git
,curl
,ls
,grep
など。
例
-
brew
は Homebrew のパッケージマネージャとして外部コマンドの例です。Homebrew自体はシェルの一部ではなく、独立したプログラムです。 -
ls
はファイルリストを表示するUnix系のコマンドで、これも外部コマンドです。
使用例
シェルを使用する際には、ビルトインコマンドと外部コマンドの区別が重要です。例えば、ビルトインのecho
コマンドはシェルが直接処理しますが、brew
コマンドはシェルがbrew
という実行ファイルを検索し、それを実行します。
まとめ
Homebrewのbrew
など、シェルに組み込まれていないコマンドを指す場合は「外部コマンド」と呼びます。外部コマンドはシェルがファイルシステム上で実行ファイルを検索し、それを子プロセスとして実行するものです。
which コマンドの挙動
- ビルトインコマンド
- シェルに組み込まれているため、特定のディレクトリには配置されない。シェルの内部で直接利用される。
- 外部コマンド
- システムの基本的なユーティリティとして提供されるため、通常は
/bin
,/usr/bin
,/usr/local/bin
などのディレクトリに配置される。
- システムの基本的なユーティリティとして提供されるため、通常は
bash-5.2$ which echo
/bin/echo
bash-5.2$ which ls
/bin/ls
bash-5.2$ which brew
/opt/homebrew/bin/brew
bash-5.2$ type echo
echo is a shell builtin
bash-5.2$ type ls
ls is /bin/ls
bash-5.2$ type brew
brew is /opt/homebrew/bin/brew
type コマンドとは
type
コマンドは、指定したコマンドがビルトインコマンド、外部コマンド、エイリアス、シェル関数など、引数で与えられたコマンドがどのような種類のコマンドであるかを表示するために使用されるコマンド**。type
コマンドは、シェルが指定されたコマンドをどのように解釈するかを確認するのに役立つ。
- ビルトインコマンドの場合
bash-5.2$ type echo
echo is a shell builtin
- 外部コマンドの場合
bash-5.2$ type ls
ls is /bin/ls
bash-5.2$ type brew
brew is /opt/homebrew/bin/brew
-a
オプションを使用すると、システム内の同じ名前のコマンドを全て表示する。このとき、ビルトインと外部コマンドの両方を含む。
例えば、echo
コマンドは、Bash などのシェルではビルトインコマンドとしても存在するが、システムの基本的なユーティリティとして外部コマンド(例えば /bin/echo
)としても存在するため、2 つ表示される。一方、ls
コマンドは、システムの基本的なユーティリティとしての外部コマンドのみ存在し、Bash には組み込まれていないため、外部コマンドの表示のみになる。
bash-5.2$ type -a echo
echo is a shell builtin
echo is /bin/echo
bash-5.2$ type -a ls
ls is /bin/ls
ちなみにシステム内にコマンドが見つけられないと not found
を返す。
bash-5.2$ type lss
bash: type: lss: not found
echo
コマンドについて、which echo
の結果と type echo
の結果が矛盾してない?which コマンドの結果は外部コマンドだけど、type コマンドの結果はビルトインコマンドだと示しているよ。
なるほど、自分の理解が足りなかった。which
コマンドはそもそも PATH
を見て外部コマンドを検索するためのコマンドだったのか。PATH
がそもそも外部コマンドを検索するための環境変数だったのか。ただ「コマンド」としてしか理解できてなかった。
- コマンド
- ビルトインコマンド
- 外部コマンド
- シェル関数
- alias
ご指摘の通り、echo
コマンドについて which echo
と type echo
の結果が異なることがあります。これは which
コマンドと type
コマンドがコマンドの検索方法や表示方法が異なるためです。
which
コマンド
which
コマンドは、指定されたコマンドがシステムのどこにあるかを表示します。具体的には、環境変数 PATH
に設定されたディレクトリを順に検索し、最初に見つかった実行可能ファイルのパスを表示します。which
コマンドは外部コマンドの検索に特化しています。
$ which echo
/bin/echo
この結果は、/bin/echo
という外部コマンドが存在することを示しています。
type
コマンド
type
コマンドは、指定されたコマンドがビルトインコマンド、外部コマンド、エイリアス、シェル関数のいずれであるかを表示します。type
コマンドは、シェルが実際にどのようにそのコマンドを解釈するかを示します。
$ type echo
echo is a shell builtin
この結果は、シェルが echo
をビルトインコマンドとして扱っていることを示しています。
なぜ結果が異なるのか
-
which
コマンド:- 外部コマンドを探してそのパスを表示します。
-
echo
のようなコマンドは、外部コマンドとして存在する場合、そのパスを表示します。
-
type
コマンド:- コマンドがビルトインコマンド、エイリアス、シェル関数、または外部コマンドのどれであるかを表示します。
- ビルトインコマンドが存在する場合、シェルはそれを優先的に使用するため、
type echo
はecho is a shell builtin
と表示します。
具体的な例
$ which echo
/bin/echo
$ type echo
echo is a shell builtin
この例では、シェルの echo
コマンドはビルトインコマンドとして定義されていますが、/bin/echo
という外部コマンドも存在します。シェルはビルトインコマンドを優先するため、実際にはビルトインの echo
が使用されます。
まとめ
which
と type
の結果が異なる理由は、which
が外部コマンドのみを検索するのに対し、type
がシェルの内部でどのようにコマンドが定義されているかを示すためです。シェルがビルトインコマンドを持つ場合、type
はそれを示しますが、which
は外部コマンドのパスを表示します。この違いを理解することで、シェルの動作を正確に把握することができます。
では、名前解決はどのように行われる?
名前解決の順序としては、ビルトインコマンドが優先される。
- ビルトインコマンドを探す。見つかれば実行する。
- ビルトインコマンドが存在しなければ、環境変数
PATH
を参照し、一番はじめに見つかった外部コマンドを実行する。
せっかくなので Bash のソースコードからコマンドの名前解決するところを見てみる
おそらく以下のような感じになってるはず。
- 入力を受け取る
- 入力をパースする
- コマンドを実行する
- ビルトインコマンドを探す。見つかれば実行する。
- ビルトインコマンドが存在しなければ、環境変数 PATH を参照し、一番はじめに見つかった外部コマンドを実行する。
ソースコードのビルド
gh repo clone ...
cd ./bash
./configure
#...
# configure: creating ./config.status
# config.status: creating Makefile
# config.status: creating builtins/Makefile
# config.status: creating lib/readline/Makefile
# config.status: creating lib/glob/Makefile
# config.status: creating lib/intl/Makefile
# config.status: creating lib/malloc/Makefile
# config.status: creating lib/sh/Makefile
# config.status: creating lib/termcap/Makefile
# config.status: creating lib/tilde/Makefile
# config.status: creating doc/Makefile
# config.status: creating support/Makefile
# config.status: creating po/Makefile.in
# config.status: creating examples/loadables/Makefile
# config.status: creating examples/loadables/Makefile.inc
# config.status: creating examples/loadables/Makefile.sample
# config.status: creating examples/loadables/perl/Makefile
# config.status: creating support/bash.pc
# config.status: creating support/bashbug.sh
# config.status: creating config.h
# config.status: executing po-directories commands
# config.status: creating po/POTFILES
# config.status: creating po/Makefile
# config.status: executing stamp-h commands
とりあえず Makefile が生成された。あとは make
するだけ。その前に Makefile の構造をざっと眺める。
- エントリーポイント:
all: .made
# Make sure the first target in the makefile is the right one
all: .made
.made
.made: $(Program) bashbug $(SDIR)/man2html$(EXEEXT)
@echo "$(Program) last made for a $(Machine) running $(OS)" >.made
$(Program): $(OBJECTS) $(BUILTINS_DEP) $(LIBDEP) .build
$(RM) $@
$(PURIFY) $(CC) $(BUILTINS_LDFLAGS) $(LIBRARY_LDFLAGS) $(LDFLAGS) -o $(Program) $(OBJECTS) $(LIBS)
ls -l $(Program)
-$(SIZE) $(Program)
make
コマンドを実行すると、all
ターゲットから .made
ターゲットが呼び出され、$(Program)
ターゲットがビルドされる。$(Program)
ターゲットのビルドが完了すると、.made
ファイルが生成される。
make
を実行してみる。
make &> log.txt
ログを見ると、最後の方に bash
をビルドし、man ページを生成しているのがわかる。
gcc -L./builtins -L./lib/readline -L./lib/readline -L./lib/glob -L./lib/tilde -L./lib/sh -g -O2 -o bash shell.o eval.o y.tab.o general.o make_cmd.o print_cmd.o dispose_cmd.o execute_cmd.o variables.o copy_cmd.o error.o expr.o flags.o jobs.o subst.o hashcmd.o hashlib.o mailcheck.o trap.o input.o unwind_prot.o pathexp.o sig.o test.o version.o alias.o array.o arrayfunc.o assoc.o braces.o bracecomp.o bashhist.o bashline.o list.o stringlib.o locale.o findcmd.o redir.o pcomplete.o pcomplib.o syntax.o xmalloc.o -lbuiltins -lglob -lsh -lreadline -lhistory -ltermcap -ltilde lib/intl/libintl.a -liconv -Wl,-framework -Wl,CoreFoundation -liconv -ldl
# ld: warning: ignoring duplicate libraries: '-liconv'
ls -l bash
# -rwxr-xr-x 1 nukopy staff 1345288 May 29 05:37 bash
size bash
# __TEXT __DATA __OBJC others dec hex
# 901120 81920 0 4295393280 4296376320 100158000
rm -f man2html.o
gcc -c -DHAVE_CONFIG_H -DSHELL -I/Users/nukopy/Projects/OSS/bash -I.. -Wno-parentheses -Wno-format-security -DMACOSX -I/opt/homebrew/opt/openjdk@17/include -g -O2 man2html.c
gcc -g -O2 man2html.o -o man2html -ldl
ビルドした bash のバージョンを確認し、起動した。できた。
$ ./bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.4.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ ./bash
bash-5.2$
echo
コマンドの名前解決の仕組みをソースコードで見てみる
おそらく以下のような感じになってるはず。echo
ならビルトインコマンドが実行され、ls
なら外部コマンドが実行されることを確認したい。
- Bash の起動
- 対話モード(REPL)の起動 → 3 ~ 5 のループ
- 入力を受け取る
- 入力をパースする
- コマンドを実行する
- ビルトインコマンドを探す。見つかれば実行する。
- ビルトインコマンドが存在しなければ、環境変数
PATH
を参照し、一番はじめに見つかった外部コマンドを実行する。
shell.c
エントリーポイント:shell.c
の main
関数が Bash のエントリーポイント。
-
やってること
- コマンドライン引数の解析
- 起動ファイルの読み込み
- 対話モード初期化
- 対話モード開始
-
shell.c
:main
main
関数内の reader_loop
関数を実行しているところ(L833)が対話モードを開始しているところ。
eval.c
の reader_loop()
関数
対話モードにおけるコマンドの解釈: 見たいのはコマンドの名前解決部分なので、reader_loop()
関数の中身を見ていく。reader_loop
関数は eval.c
に定義されている。eval.c
は、ソースコード 1 行目のコメントにもあるように、コマンドを読み、評価するモジュールである。
-
eval.c
:reader_loop
reader_loop
関数は、シェルの対話モードにおける主要な動作を制御している。この関数は、ユーザーからのコマンド入力を受け付け、それをパース(解析)し、実行し、そして再び入力を受け付ける、という一連の流れを while ループで繰り返す。
以下に、その主要なステップを抽出する:
-
コマンド入力の受け付け
- このステップは、
reader_loop
関数のwhile (EOF_Reached == 0)
ループが始まるところから始まる。このループは、EOF(End Of File、つまり入力の終わり)が検出されるまで繰り返される。つまり、ユーザからの新しいコマンド入力を待ち続ける。
- このステップは、
-
コマンドのパース(解析)
- このステップは、
read_command()
関数の呼び出しによって行われる。この関数は、ユーザーからの入力を読み込み、それをパース(解析)してコマンドとしての形式に変換する。パースされたコマンドは、current_command = global_command
行によってcurrent_command
変数に格納される。
- このステップは、
-
コマンドの実行
- このステップは、
execute_command(current_command)
関数の呼び出しによって行われる。この関数は、パースされたコマンド(current_command
変数に格納されている)を実行する。
- このステップは、
-
コマンドの実行結果の処理 & 次の入力の準備
-
execute_command
関数の実行結果(つまり、実行されたコマンドの終了ステータス)は、last_command_exit_value
というグローバル変数に格納される。 - そして再度入力受け付けモードに移る前に、
QUIT
マクロが呼び出される。このマクロは、シェルが終了するべきかどうかをチェックする。具体的には、last_command_exit_value
が非ゼロ(つまり、直前のコマンドがエラーで終了した)場合、シェルは終了する。 - その後、
dispose_command
関数が呼び出されてcurrent_command
が解放され、再び入力を受け付ける状態に戻る。
execute_command (current_command); exec_done: QUIT; if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } ...
- このコードは、
execute_command
の実行結果を処理し、次のコマンド入力を待つための準備を行っている。
-
-
再び入力受付モードへ
- コマンドの実行が終わると、
reader_loop
関数は再びwhile (EOF_Reached == 0)
ループの先頭に戻り、新たなコマンド入力の受け付けを開始する。
- コマンドの実行が終わると、
以上が、reader_loop
関数における主要なステップである。この関数は、シェルの対話モードにおける基本的な動作を制御する。
Q. あれ、でも終了ステータスがエラーでもシェルの対話モードって終わらなくない?ここでの last_command_exit_value っていうのは終了ステータス(POSIX なら 0 が成功、1 ~ 255 がエラー)とは違うの?
last_command_exit_value
は、最後に実行されたコマンドの終了ステータスを保持します。これはPOSIXの規定に従い、0が成功を示し、1から255までの値がエラーを示します。シェルの対話モードでは、コマンドがエラーで終了したとしてもシェル自体は終了しません。しかし、シェルスクリプトの中で
set -e
オプションが設定されている場合(これはerrexit
オプションとも呼ばれます)、エラーで終了したコマンドが実行されるとシェルスクリプト全体が終了します。したがって、
last_command_exit_value
が非ゼロ(エラー)である場合でも、シェルの対話モードは終了しません。ただし、シェルスクリプト内でエラーが発生した場合、その挙動はスクリプトがset -e
を設定しているかどうかによります。
たしかに以下のシェルスクリプトを実行したらシェルが終了した。
set -e
lss # 存在しないコマンド
Q. そっか対話モードだけでなく、単純にシェルスクリプトのインタプリタとして動作するモードもあるのか。
はい、その通りです。シェルは対話モードだけでなく、シェルスクリプトのインタプリタとしても動作します。対話モードでは、ユーザーからの入力を受け付けてコマンドを実行します。一方、シェルスクリプトのインタプリタとして動作する場合、シェルスクリプトファイルを読み込み、その中に書かれたコマンドを順に実行します。
具体的には、シェルを起動する際にシェルスクリプトファイルを引数として渡すと、シェルはそのファイルを読み込み、ファイル内のコマンドを順に実行します。このとき、シェルは対話モードではなく、非対話モードで動作します。
例えば、次のようにシェルを起動すると、
script.sh
というシェルスクリプトを実行します。bash script.sh
この
script.sh
ファイル内には、実行したいコマンドが順番に書かれています。シェルはこれらのコマンドを上から順に実行し、スクリプトの最後まで到達したら終了します。
コードよくみると、reader_loop
内でインタラクティブシェルかどうかのフラグで処理を分岐している部分があるね。
std::process::Command
Command.new("ls")
という初期かメソッドを実行したときにはコマンドは実行されず、Command.output()
、Command.status()
、Command.spawn()
が実行されたときにコマンドが実行される。
std::process::Command.output()
の中身
※UNIX 系 OS、特に macOS 前提で進める
-
std::process::Command.output()
(共通の実装process.rs
)-
std::process::Command.inner.output()
(共通の実装process.rs
)-
std::process_unix::Command.output()
(ここで各 OS の実装に飛ぶ。process_unix.rs
)std::process_unix::Command.spawn()
-
-
std::process_unix::Command.spawn()
の中で何が行われているか?
- 親プロセス(
std::process::Command::new("some cmd")
を呼んだプロセス)、子プロセス("some cmd"
を実行するために fork で生成されるプロセス)の標準入出力のオブジェクトを取得する:std::process_common::Command.setup_io()
- 子プロセスのコマンドを実行するとき、コマンドの結果を取得するためにここで取得した子プロセスの標準入出力を使用する
- プロセスのフォーク:
std::process_unix::Command.do_fork()
(中でlibc::fork()
を呼んでいる)impl Command { unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { cvt(libc::fork()) }
- jjj
- j
std::process::ExitCode と std::process::ExitStatus の違い
どちらもプロセスの終了状態を扱うための API。以下、公式ドキュメントより引用:
Differences from
ExitStatus
ExitCode
is intended for terminating the currently running process, via theTermination
trait, in contrast toExitStatus
, which represents the termination of a child process. These APIs are separate due to platform compatibility differences and their expected usage; it is not generally possible to exactly reproduce anExitStatus
from a child for the current process after the fact.
引用元:https://doc.rust-lang.org/std/process/struct.ExitCode.html#differences-from-exitstatus
ExitStatusとの違い
ExitCode
は、Termination
トレイトを介して現在実行中のプロセスを終了させることを目的としています。これに対して、ExitStatus
は子プロセスの終了状態を表します。これらの API はプラットフォーム互換性の違いや使用目的の違いから分けられています。現在のプロセスの終了状態として子プロセスのExitStatus
を正確に再現することは一般的にできません。
日本語訳 by ChatGPT 4o
現在のプロセスの終了状態として子プロセスの
ExitStatus
を正確に再現することは一般的にできません。
ここがまだ理解できない。
std::process::ExitCode
ExitCode
は、現在実行中のプロセスの終了コードを表す。これは、Rust プログラムが終了するときに特定の終了コードを返すために使用される。
- 目的:現在実行中のプロセスを終了させるためのコードを表す
-
使用例:
std::process::exit
関数やTermination
トレイトを実装する際に使用される
例:
use std::process::ExitCode;
fn main() -> ExitCode {
// プロセスの終了コードとして 1 を返す
ExitCode::from(1)
}
std::process::ExitStatus
ExitStatus
は、(現在実行中の親プロセスの)子プロセスの終了状態を表す。これは、Rust プログラムが子プロセスを生成し、そのプロセスが終了したときの状態を取得するために使用される。
- 目的:子プロセスの終了状態を表す
-
使用例:
std::process::Command
を使って子プロセスを生成し、そのプロセスが終了した後の状態を取得するために使用される
例:
use std::process::Command;
fn main() {
let status = Command::new("ls")
.status()
.expect("failed to execute process");
if status.success() {
println!("Process exited successfully");
} else {
println!("Process exited with error code: {:?}", status.code());
}
}
違いの詳細
公式ドキュメントの記述にある通り、ExitCode
と ExitStatus
には以下のような違いがある。
- 使用目的の違い
-
ExitCode
は、現在実行中のプロセスを終了させるために使用される。主に、Rust プログラム自体が終了コードを返す場合に使用される。 -
ExitStatus
は、子プロセスの終了状態を表す。これは、親プロセスが子プロセスを生成し、その終了状態を取得して処理するために使用される。
-
- API の分離
- これらの API は、プラットフォーム互換性の違いや使用方法の違いにより分離されている。現在のプロセスの終了コードを子プロセスの終了コードとして再現することは一般的にできない。例えば、システムによっては、現在のプロセスの終了コードを直接取得する方法が異なるためである。
- 実装の違い
-
ExitCode
は、現在のプロセスの終了に特化しており、Rust のTermination
トレイトを介して使用される -
ExitStatus
は、子プロセスの終了状態を詳細に扱うために設計されており、標準ライブラリのCommand
構造体を使用して生成されたプロセスの終了状態を取得する
-
まとめ
-
ExitCode
:現在のプロセスの終了コードを表し、プログラムの終了時に使用される -
ExitStatus
:子プロセスの終了状態を表し、親プロセスが子プロセスの終了状態を取得して処理するために使用される
これらの API は異なる目的で使用され、プラットフォーム互換性や使用方法の違いから明確に分けられている。
Q
この文章もうちょっとわかりやすく解説して。
現在のプロセスの終了状態として子プロセスのExitStatusを正確に再現することは一般的にできません。
A
以下 ChatGPT の解答
子プロセスの ExitStatus
を、現在のプロセスの終了状態として正確に再現することができない理由は、いくつかの技術的な制約とプラットフォーム依存の違いに起因します。以下にその主要な理由を挙げます。
プラットフォーム依存の違い
-
終了状態の情報の違い:
- 子プロセスが終了すると、その終了状態は親プロセスに通知されますが、終了状態にはプラットフォームごとに異なる情報が含まれることがあります。例えば、Unix系システムではプロセスの終了コードに加えて、シグナルによる終了情報も含まれますが、Windowsでは異なるメカニズムが使われます。
- これにより、子プロセスの
ExitStatus
を現在のプロセスの終了状態として正確に再現することが難しくなります。
現在のプロセスの終了状態の制御
-
直接的な終了制御:
-
ExitCode
は現在のプロセスが終了する際に設定するものです。現在のプロセスの終了状態を設定するには、通常はstd::process::exit
を使います。この関数はプロセスの終了コードを直接設定しますが、子プロセスのExitStatus
に含まれる詳細な情報(例えば、シグナル情報など)をそのまま反映することはできません。
-
-
プロセス管理の違い:
- Rustでは、現在のプロセスと子プロセスの終了管理には異なるAPIが提供されています。
ExitCode
は現在のプロセス専用であり、ExitStatus
は子プロセス専用です。これらのAPIは、プラットフォームの互換性や使用目的に応じて分けられており、現在のプロセスが終了する際に子プロセスの終了情報を正確に模倣することは設計上想定されていません。
- Rustでは、現在のプロセスと子プロセスの終了管理には異なるAPIが提供されています。
実装の例
ExitCode
)
現在のプロセスの終了(use std::process::ExitCode;
fn main() -> ExitCode {
// 現在のプロセスを終了コード1で終了
ExitCode::from(1)
}
ExitStatus
)
子プロセスの終了(use std::process::Command;
fn main() {
let status = Command::new("ls")
.status()
.expect("failed to execute process");
if status.success() {
println!("Process exited successfully");
} else {
println!("Process exited with error code: {:?}", status.code());
}
}
まとめ
-
プラットフォーム依存の違い: プロセスの終了状態にはプラットフォームごとに異なる情報が含まれるため、子プロセスの
ExitStatus
を現在のプロセスの終了状態として正確に再現することは難しいです。 -
現在のプロセスと子プロセスの終了制御の違い:
ExitCode
とExitStatus
はそれぞれ異なる目的と制御メカニズムを持つため、設計上これらを混同して使用することはできません。
これらの理由から、子プロセスの ExitStatus
をそのまま現在のプロセスの終了状態として再現することは一般的にできないのです。
なるほど、ExitCode では ExitStatus は再現できないよってことか。
はい、その通りです。ExitCode
ではExitStatus
を再現できない理由は以下の通りです。
詳細な説明
-
ExitCode
: 現在のプロセスを終了させるためのコードです。これは、Rustプログラムが終了するときに特定の終了コードを返すために使用されます。シンプルに終了コードを設定するためのAPIです。 -
ExitStatus
: 子プロセスが終了したときの詳細な終了状態を表します。これは、終了コードに加えて、例えばシグナルによる終了などの詳細な情報も含むことができます。ExitStatus
は、子プロセスの終了状態を詳細に知るためのAPIです。
再現の難しさ
-
プラットフォーム固有の情報:
ExitStatus
にはプラットフォーム固有の詳細情報が含まれています。例えば、Unix系システムではシグナルによる終了情報などが含まれますが、これを現在のプロセスの終了状態として再現することは難しいです。 -
APIの目的の違い:
ExitCode
は現在のプロセスの終了を簡潔に表すためのものであり、ExitStatus
のような詳細な情報を再現することを目的としていません。これらのAPIは異なる目的で設計されているため、ExitCode
でExitStatus
の詳細な情報を再現することはできません。
公式ドキュメントの要約
公式ドキュメントの記述を要約すると、以下のようになります。
-
違い:
ExitCode
は現在のプロセスの終了を表すためのものであり、ExitStatus
は子プロセスの終了状態を表すものです。 - 分離の理由: これらのAPIは、プラットフォームの互換性の違いや使用目的の違いにより分けられています。
-
再現の困難さ: 一般的に、子プロセスの
ExitStatus
を現在のプロセスの終了状態として後から再現することはできません。
具体的な例
以下は具体的な例です。
use std::process::{Command, ExitCode};
// 現在のプロセスの終了コードを設定
fn main() -> ExitCode {
// ここでは終了コード1を設定
ExitCode::from(1)
}
// 子プロセスの終了状態を取得
fn main() {
let status = Command::new("ls")
.status()
.expect("failed to execute process");
if status.success() {
println!("Process exited successfully");
} else {
println!("Process exited with error code: {:?}", status.code());
}
}
このように、ExitCode
とExitStatus
は異なる目的で使用され、それぞれが異なる役割を果たしています。
chap01 done
chap2: echo