The shell
src/shell.rs.
#![allow(unused)] fn main() { pub struct Shell { pub nth: usize, // the order it joined in, counting from zero pub pid: Pid, pub shlvl: u32, pub subshell: u32, // $BASH_SUBSHELL pub joined: Stamp, // when it joined, on both clocks pub bash: Bash, // which bash, and how it was invoked pub options: Options, // what it had switched on then, which may change after pub brought: Vec<String>, // the words its join carried, verbatim } pub struct Bash { pub version: Version, pub binary: PathBuf, pub zero: String, pub invocation: Invocation } pub struct Invocation { pub command: Option<String>, pub standard_input: bool, pub interactive: bool } pub struct Options { pub flags: Flags, pub shellopts: Vec<String>, pub bashopts: Vec<String> } }
A shell opens with an account of itself, stated by the shell rather than
inferred from what it went on to write. The account travels with the
announcement, on the control fifo, in frames, before the shell's pipe is
opened, so the run knows the whole of it before releasing the shell. The
account is what makes a shell, and a Message presupposes one, so the account
is never a message.
One prelude function builds it, and reads as the checklist of what a shell states about itself. These are the shipped bytes:
__bc_account() {
declare __bc_out=$1 IFS=' '
declare -a __bc_meta="(${__BC__META[$2]-})"
set -- "at=$EPOCHREALTIME" \
pid "$BASHPID" \
shlvl "$SHLVL" \
subshell "$BASH_SUBSHELL" \
versinfo "(${BASH_VERSINFO[*]@Q})" \
bash "$BASH" \
zero "$0" \
flags "$-" \
shellopts "$SHELLOPTS" \
bashopts "$BASHOPTS" \
command "${BASH_EXECUTION_STRING-}" \
brought "(${__bc_meta[*]@Q})"
printf -v "$__bc_out" '(%s)' "${*@Q}"
}
One array literal, clock first and no verb, written into the caller's local.
Every entry is passed as bash reports it, and Shell::of decides what any of
it means. Adding a fact is a word here and a field there.
brought is the entry the client writes: the words its join carried, from
BC_JOIN LABEL DIR word…, as one nested literal in the shape versinfo
takes, landing verbatim on Shell::brought. It is an arglist like a
message's. The protocol reserves no word in it, and key value pairs read
with field are the client's own convention. A fork's reattach announces its
label's words, and a child process re-derives them at its own join.
None of this changes while a shell lives. A subshell has a $BASHPID of its
own and joins as a shell of its own, and set refuses -i, -c and -s, so
Invocation is settled at startup. Options is a snapshot, since a subject
may set -e at any point.
Why a shell states what it is
A walk cannot be read without it. Bash writes $0 into BASH_SOURCE for code
it was given rather than read from a file, and main there for anything
defined at an interactive prompt — words a script can also produce. Telling
those apart is a property of the shell, and the shell is what knows it:
Invocation::from_a_file is command.is_none() && !standard_input.
An interactive shell joins by typing its own way in, loading the pieces and
saying the init, because bash reads BASH_ENV for non-interactive shells
only. The mechanism is indifferent to this: the same BC_JOIN runs however
the shell got there, under either orchestration. See
scoping.md and stack.md.
What the account leaves out
Who forked whom. A fork inherits its parent's pipe descriptor and takes its
own on its first word; its descent from a particular shell is not reported,
because bash does not track it either. What bash does know, $SHLVL and
$BASH_SUBSHELL, is reported as bash states it.