A full-featured Linux shell written in C, Flex, and Bison.
This project is a personal learning effort and a challenge I took on to dive
deeper into systems programming and to understand how a real shell works.
It is inspired by Bash and aims to feel familiar in its features and behavior,
while being designed and implemented completely from scratch.
- Full command language: Supports commands, pipelines, I/O redirections, background execution, quoting, escape sequences, and comments.
- Shell and environment variables: Supports shell and environment variables, with builtins to define, update, and remove them.
- Aliases: Allows defining and removing command aliases through builtins.
- Shell expansions: Supports tilde expansion, parameter expansion, command substitution, and filename expansion (globbing).
- Interactive mode: Provides a full interactive shell experience with a dynamic prompt, advanced line editing, persistent command history, and context-aware tab completion.
- Job control: Supports foreground and background job management, including listing, suspending, resuming, and signaling jobs.
- Script execution: Supports non-interactive script execution and sourcing scripts into the current shell environment.
- Startup files and initialization: Implements login and non-login shell initialization by setting up the default environment and running startup scripts.
- π 1 Getting Started
- π€ 2 Shell Language
- βοΈ 3 Command Execution
- ποΈ 4 Variables and Environment
- π·οΈ 5 Aliases
- πͺ 6 Shell Expansions
- π₯οΈ 7 Interactive Shell Mode
- π¦ 8 Job Control
- π 9 Running Scripts
- π§ 10 Startup Files and Initialization
- π§° 11 Other Builtins
- β 12 Testing
- π€ 13 Contributing
- π 14 License
- π 15 Related Projects
This section covers everything you need to build, install, and run xd-shell
from the command line.
To use xd-shell, you need a Linux environment with the following tools
installed:
- Git (to clone the repository)
- Flex (lexer generator)
- Bison (parser generator)
- GCC (C compiler)
- Make (build system)
Below are example installation commands for Debian/Ubuntu-based systems:
sudo apt install gitsudo apt install flexsudo apt install bisonsudo apt install gccsudo apt install makeClone the repository and build the project as follows:
git clone https://github.com/xduraid/xd-shell.git
cd xd-shell
makeThis produces the xd_shell executable in ./bin/. You can run it
directly without installing it with ./bin/xd_shell.
To install the shell system-wide, run:
sudo make installBy default, this installs the xd_shell executable into /usr/local/bin/.
βΉοΈ Note: If you want to uninstall it later, run:
sudo make uninstall.
The shell can be invoked as follows:
xd_shell [-l] [-c string | script]When started without arguments, the shell reads commands from standard input.
If both standard input stdin and standard output stdout are connected to
a terminal, the shell runs in interactive mode (see
Interactive Shell Mode).
A script file may be specified as an argument to read commands from that file, or
the -c option may be used to read commands from a string argument (see
Running Scripts).
When invoked with the -l option, the shell runs as a login shell and performs
login-specific initialization before processing any commands (see
Login Shell Detection and
Startup Files).
When the shell exits, it exits with the status of the last command executed.
xd-shell provides a command language supporting commands, pipelines,
I/O redirections, background execution, quoting, escape sequences, and comments.
This section describes the syntax and interpretation rules of the shell language.
A command consists of a command name (which may refer to a builtin or an external program), zero or more arguments, and optional I/O redirections (see Redirections).
command [arg ...] [redirection ...]
A pipeline is a sequence of one or more commands separated by the pipe character
(|). In a pipeline with multiple commands, the standard output of each command
is connected to the standard input of the next command.
A pipeline may optionally end with &, indicating that it will be executed
in the background.
cmd [| cmd ...] [&]
Quoting and escaping determine how the shell treats special characters. xd-shell
supports single quotes, double quotes, backslash escaping, and line continuation.
Single quotes preserve the literal value of every character inside them. A single quote cannot appear inside a single-quoted string, even when preceded by a backslash.
Double quotes preserve the literal value of all characters except $, ", and
\. Inside double quotes, a backslash can be used to escape $, ", or \.
When a backslash appears before one of these characters, the backslash is
removed and the following character is preserved literally. A backslash before
any other character inside double quotes is left unchanged.
Outside of quotes, a backslash (\) preserves the literal value of the next
character by removing any special meaning it would otherwise have. The backslash
is removed, and the following character is left unchanged.
A backslash (\) immediately followed by a newline (\n) is treated as a line
continuation. Both characters are removed, allowing a command or pipeline to
span multiple physical lines.
Redirections change the source of standard input stdin and the destinations of
standard output stdout and standard error stderr.
The shell supports the following redirections:
| Redirection | Description |
|---|---|
< file |
Redirect stdin from file |
> file |
Redirect stdout to file (truncate) |
>> file |
Redirect stdout to file (append) |
2> file |
Redirect stderr to file (truncate) |
2>> file |
Redirect stderr to file (append) |
>& file |
Redirect both stdout and stderr to file (truncate) |
>>& file |
Redirect both stdout and stderr to file (append) |
Redirections are processed from left to right. When multiple redirections modify the same stream, the last one takes effect.
βΉοΈ Note: Only filenames or words that expand to filenames may be used as redirection targets (
file).
A comment is text that is ignored by the shell, it begins with the #
character and continues to the end of the line. Comments are recognized only
when the # appears outside single quotes ('...') or double quotes ("...").
After reading the input line, the shell processes it in three main phases: scanning, parsing, and execution.
During the scanning phase, the input line is converted into a stream of tokens by the lexer. As part of this phase, alias expansion is applied to the command word of each command (see Alias Expansion). The token stream is then passed to the parser.
During the parsing phase, the token stream is analyzed according to the shell grammar and transformed into internal command and pipeline structures, as described in Commands and Pipelines. As part of this process, shell expansions are applied to each word token (see Shell Expansions), producing the final command names, arguments, and redirection targets. Once a complete pipeline has been parsed, it is passed to the execution phase.
During the execution phase, each command in the pipeline is prepared and run. Any redirections associated with a command are applied first, establishing the appropriate standard input, output, and error streams. The shell then determines whether the command is a builtin or an external command.
For external commands, if the command name contains a /, it is treated as an
explicit path and used as-is. Otherwise, the shell searches the directories
listed in the $PATH environment variable to locate the executable. If the
executable is not found, the shell checks for an executable with that name in
the current working directory.
Commands that are part of the same pipeline normally run concurrently, each in its own process, with pipes connecting their standard input and output streams. Pipelines consisting of a single builtin command are executed directly within the shell process so that their effects (such as variable assignments or directory changes) persist in the current shell environment.
By default, the shell waits for the pipeline to complete before reading the next
command. If the pipeline is terminated with &, it is executed in the
background, and the shell continues reading input without waiting for it.
When running in interactive mode, each executed pipeline is registered with the job control system. This enables foreground and background execution, terminal control, and signal handling, as described in Job Control.
The exit status of a pipeline is the exit status of the last command executed in the pipeline.
xd-shell provides a variable system for storing named values within the shell.
These variables may be referenced in commands and are expanded during parameter
expansion (see Parameter Expansion). Some variables also
affect the behavior of the shell and the execution environment of commands.
The shell distinguishes between two kinds of variables: shell variables and environment variables. Shell variables exist only within the current shell instance, while environment variables are additionally included in the environment of external commands executed by the shell.
This section describes how variables are defined, updated, exported, and removed, as well as the rules governing valid variable names.
Shell variables are name-value pairs that exist within the current shell instance.
They are local to the shell and are not automatically included in the environment of external commands.
Shell variables may be defined, updated, and removed using the set and
unset builtins.
The set builtin is used to define or update shell variables, or to display
existing ones.
Usage:
set [name[=value] ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
-
Without arguments:
Prints all defined variables in the following shell-reusable form:set name1='value1' set name2='value2' ... set nameN='valueN'
-
With arguments:
Each argument must be in one of the following forms:nameβ prints the variablenamein the shell-reusable form.name=valueβ defines or updates the variablenametovalue, preserving its exported status (see Environment Variables).
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The unset builtin is used to remove variables.
Usage:
unset name [name ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
For each argument name, the corresponding variable is removed if it exists.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
Environment variables are shell variables that have been marked for export and are included in the environment of external commands executed by the shell.
Environment variables may be defined, exported, and removed using the export
and unexport builtins.
The export builtin is used to mark variables for export so that they are
included in the environment of subsequently executed external commands.
Usage:
export [name[=value] ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
-
Without arguments:
Prints all exported variables in the following shell-reusable form:export name1='value1' export name2='value2' ... export nameN='valueN'
-
With arguments:
Each argument must be in one of the following forms:nameβ marks the variablenameas exported.name=valueβ defines or updates the variablenametovalue, and marks it for export.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The unexport builtin is used to remove the export attribute from variables so
that they are no longer included in the environment of subsequently executed
external commands.
Usage:
unexport name [name ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
For each argument name, the export attribute of the corresponding
variable is removed.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
Variable names may contain letters, digits, and underscores, but must not begin with a digit.
βΉοΈ Note: Variable names are case-sensitive.
xd-shell provides a mechanism for defining command aliases. Aliases allow
commands or command pipelines to be referenced by shorter or more convenient
names. They may also be used to override existing commands with alternative
behavior.
An alias associates a name with a replacement text. When a command is read, the shell checks whether the command word matches a defined alias and performs alias expansion accordingly.
Aliases are maintained within the current shell instance and are managed using
the alias and unalias builtins.
This section describes how aliases are defined, expanded, and removed, as well as the rules governing valid alias names.
Alias names follow the same naming rules as shell variable names. They may contain letters, digits, and underscores, but must not begin with a digit.
βΉοΈ Note: Alias names are case-sensitive.
Alias expansion is performed during the scanning phase of command processing, before parsing and execution (see Command Execution).
For each command, the shell examines its command word. If it matches the name of a defined alias, it is replaced by the corresponding alias text.
The result of alias expansion is rescanned for further alias expansion. This process continues until no further expansions are possible or an alias expands to itself.
If an alias expands to multiple words, the resulting text is processed as if it had been entered directly by the user.
βΉοΈ Note: Alias expansion applies only to unquoted command words. Words that appear in other positions, such as arguments or redirection targets, are not subject to alias expansion.
The alias builtin is used to define or update aliases, or display existing
ones.
Usage:
alias [name[=value] ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
-
Without arguments:
Prints all defined aliases in the following shell-reusable form:alias name1='value1' alias name2='value2' ... alias nameN='valueN'
-
With arguments:
Each argument must be in one of the following forms:nameβ prints the aliasnamein the shell-reusable form.name=valueβ defines or updates the aliasnameto the replacement textvalue.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The unalias builtin is used to remove aliases.
Usage:
unalias name [name ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
For each argument name, the corresponding alias is removed if it exists.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
xd-shell provides a set of expansion mechanisms that transform words in a
command into their final form before execution. These expansions allow the shell
to resolve variable references, substitute special patterns, incorporate the
output of commands into the command, and produce multiple words from a single
input word.
The shell supports the following expansions, which are performed during the parsing phase of command processing (see Command Execution), in the following order:
- Tilde Expansion
- Parameter Expansion
- Command Substitution
- Word Splitting
- Filename Expansion
- Quote Removal
This section describes each type of expansion and the rules governing its behavior.
Tilde expansion provides a convenient way to refer to the home directory of the
current user or other users using ~ at the beginning of a word, instead of
specifying an absolute path.
The shell supports the following forms of tilde expansion:
-
~and~/path
Expands~to the current user's home directory.
If$HOMEis set, its value is used. Otherwise, the home directory is obtained from the system's user database. -
~userand~user/path
Expands~userto the home directory of the useruser.
If no such user exists, the word is left unchanged. -
~+and~+/path
Expands~+to the current working directory$PWD.
If$PWDis unset, the word is left unchanged. -
~-and~-/path
Expands~-to the previous working directory$OLDPWD.
If$OLDPWDis unset, the word is left unchanged.
Tilde expansion is applied only when ~ appears at the beginning of an
unquoted word, and only within the first path component (that is, before the
first /). A ~ appearing in any other position, or within single or double
quotes, is treated as a literal character.
βΉοΈ Note:
$PWDand$OLDPWDare maintained by Thecdbuiltin.
Parameter expansion replaces references to parameters with their corresponding values. Parameters include shell variables, environment variables, and special parameters maintained by the shell.
The shell supports the following forms of parameter expansion:
$name${name}
In these forms, name refers to a shell variable, an environment variable, or
a special parameter.
A $ begins parameter expansion only when it appears in unquoted or
double-quoted text and is followed by a valid parameter name or expansion form.
Otherwise, the $ character is treated as a literal and is not expanded.
Variable expansion replaces references to shell variables and environment variables with their corresponding values.
When using the $name form, all consecutive characters that form a valid
variable name are considered part of the variable reference. To avoid ambiguity
with adjacent characters, the ${name} form may be used to explicitly delimit
the variable name.
If a referenced variable is not defined, it is replaced by an empty string.
Special parameters are predefined, read-only parameters maintained by the shell. They provide information about the state of the shell and recently executed commands.
xd-shell supports the following special parameters:
| Parameter | Description |
|---|---|
? |
Expands to the exit status of the most recently executed command |
$ |
Expands to the process ID of the current shell |
! |
Expands to the process ID of the most recently executed background job |
Special parameters are expanded using the forms described in
Parameter Expansion. Both $name and ${name} forms
are valid. For example, both $? and ${?} expand to the exit status of the
most recently executed command.
Command substitution allows the output of a command to be used as part of another
command using the form $(command).
The text inside $(...) is parsed as normal shell input and may contain
commands, pipelines, redirections, and nested command substitutions.
It is executed in a subshell (a child shell process), and the entire $(...)
construct is replaced by its standard output, with any trailing newline
characters removed.
Only the standard output stdout of the command is captured. The standard
error stderr is not affected and is passed through unchanged.
A $(...) construct is recognized as command substitution only when it
appears in unquoted or double-quoted text. Otherwise, it is treated as
literal text.
If the command produces no output, the $(...) construct expands to an
empty string.
βΉοΈ Note: Because command substitution is executed in a subshell, any side effects such as modifying variables or changing the working directory do not affect the parent shell.
Word splitting divides the text produced by expansions into separate words.
The resulting text is split using the following whitespace characters as
delimiters: space ( ), tab (\t), and newline (\n). Consecutive
whitespace characters are treated as a single delimiter, and leading and
trailing whitespaces are ignored.
Word splitting is applied only to the results of expansions that occur outside double quotes.
If an expansion results in empty text, no words are produced, since there is no content to split. However, when such an expansion occurs within double quotes, it is treated as a single empty word rather than being discarded.
Filename expansion matches patterns in words against filenames and replaces them with the names of matching files or directories.
After word splitting, each word is examined. If it contains unquoted wildcard characters, bracket expressions, or brace patterns, the word is treated as a filename pattern and is matched against filenames.
The shell supports the following constructs in filename expansion:
| Syntax | Description |
|---|---|
* |
Matches any sequence of characters, including an empty sequence |
? |
Matches exactly one character |
[abc] |
Matches a single character from those listed inside the brackets |
[a-z] |
Matches a single character from the specified character range |
[!abc] |
Matches a single character not listed inside the brackets |
[!a-z] |
Matches a single character outside the specified character range |
{a,b,c} |
Brace pattern: specifies multiple alternative patterns |
Brace patterns are expanded into multiple patterns during matching. This expansion is purely textual and does not by itself produce any output. Each resulting pattern is then matched independently.
Patterns are applied independently to each path component separated by /.
The / character is not matched by *, ?, or bracket expressions and must
appear explicitly in the pattern.
If a pattern matches one or more filenames, it is replaced by the list of matching names, in lexicographical order. Each match becomes a separate word.
Filenames beginning with . are matched only when the pattern explicitly
begins with a ..
If a pattern does not match any filenames, no expansion is performed and the word is left unchanged.
βΉοΈ Note: Brace patterns may be nested.
Quote removal removes the quote characters and backslashes that were used to preserve the literal value of text.
After all expansions have been performed, any remaining quote characters
and backslashes that did not result from the preceding expansions are
removed from each word. This includes single quotes ('), double quotes
("), and backslashes (\).
A backslash is removed when it appears outside quotes, and within double
quotes when it precedes $, ", \, or a newline, as described in
Quoting and Escaping.
xd-shell provides an interactive shell environment when both standard input
stdin and standard output stdout are connected to a terminal.
In interactive mode, the shell presents a dynamic prompt and accepts input through an interactive line editor. It maintains command history across sessions, provides context-aware tab completion, and integrates with the job control system (see Job Control).
This section describes the behavior and features available when the shell is running interactively.
In interactive mode, the shell displays a dynamic, colorized prompt that includes the current user, host, and working directory.
The prompt has the general form:
user@host:cwd$The prompt ends with $ for normal users and # for the superuser.
When the current working directory is within the user's home directory, the
$HOME prefix is replaced with ~.
The prompt is updated before each input line is read to reflect changes such as the current working directory.
The shell uses xd-readline, a custom line-editing library implemented specifically for this project.
In interactive mode, input is read through this line editor rather than directly from standard input. This provides an editing experience similar to that of modern interactive shells, allowing the user to modify the current line before it is submitted for execution.
The line editor supports cursor movement, in-line text editing, history navigation and search, and tab-triggered completion.
The line editor allows modification of the current input line before it is submitted for execution.
The editor supports cursor movement by character and by word, navigation to the beginning and end of the line, and deletion of characters or words.
The following cursor movement and editing actions are supported:
| Key Combination | Action |
|---|---|
β or Ctrl+B |
Move the cursor one character to the left |
β or Ctrl+F |
Move the cursor one character to the right |
Ctrl+β or Alt+B |
Move the cursor one word to the left |
Ctrl+β or Alt+F |
Move the cursor one word to the right |
Home or Ctrl+A |
Move the cursor to the beginning of the line |
End or Ctrl+E |
Move the cursor to the end of the line |
Backspace or Ctrl+H |
Delete the character before the cursor |
Delete |
Delete the character at the cursor |
Ctrl+D |
Delete the character at the cursor, or signal EOF if the line is empty |
Alt+Backspace |
Delete the word before the cursor |
Alt+D or Ctrl+Delete |
Delete the word after the cursor |
Ctrl+U |
Delete all text before the cursor |
Ctrl+K |
Delete all text from the cursor to the end of the line |
Ctrl+L |
Clear screen |
Enter or Ctrl+J |
Submit the current input line |
βΉοΈ Note: A word is a sequence of letters and digits. Word-based operations use this definition when moving the cursor or deleting text.
The line editor allows previously executed commands to be recalled from the command history by navigating through the history list sequentially (see Command History).
By moving backward and forward through the history list, the user may recall or modify commands in the order they were executed, without retyping them from scratch. When a history entry is recalled, it becomes the current editable line and may be modified before execution.
The following history-navigation actions are supported:
| Key Combination | Action |
|---|---|
β or Page Up |
Move to the previous history entry |
β or Page Down |
Move to the next history entry |
Ctrl+β or Ctrl+Page Up |
Jump to the first (oldest) history entry |
Ctrl+β or Ctrl+Page Down |
Jump to the last (most recent) history entry |
The line editor allows previously executed commands to be recalled from the command history by matching text within them (see Command History). Instead of traversing entries sequentially, the user can jump directly to commands that contain a given search string.
The search is incremental: as the user types the search text, the current match is updated and highlighted in real time. The user may search in either the reverse (older entries) or forward (more recent entries) direction, and may accept, skip, or cancel matches.
When a match is accepted, it becomes the current editable line and may be modified before execution.
The following history-search actions are supported:
| Key Combination | Action |
|---|---|
Ctrl+R |
Start reverse search or jump to the previous match |
Ctrl+S |
Start forward search or jump to the next match |
Ctrl+G |
Cancel the search and restore the original input line |
Esc Esc |
Accept the current match and exit search mode |
The line editor supports tab-triggered completion to assist in writing commands.
When the Tab key is pressed, the line editor requests completions for the
current word at the cursor. The shell generates and returns a set of
completions based on the word and its position within the command line (see
Tab Completion Generation).
After receiving the completions, the line editor:
- Completes the word fully if there is a single completion.
- Inserts the longest common prefix if there are multiple completions.
- Displays all completions on subsequent
Tabpresses.
xd-shell maintains both in-memory and persistent command history. History is
automatically loaded at startup, used during the session, and saved when the
shell exits.
During an interactive session, every executed command is appended to an internal history list. Consecutive identical commands are not duplicated, and commands that begin with leading whitespace are not added. The history list holds up to 1000 entries, discarding the oldest ones when the limit is reached. Recalled history lines can be edited or re-executed, and the in-memory list is used directly by history navigation and incremental search.
When the shell starts, it attempts to load command history from the file
specified by $HISTFILE. If $HISTFILE is unset, xd-shell first sets it to
$HOME/.xdsh_history and then attempts to load the history file. When the shell
exits, the entire in-memory history list is written to the file indicated by
$HISTFILE, unless the variable was unset during the session.
The history builtin is used to display and manipulate the shell's command
history.
Usage:
history [-c]
history -a [file]
history -r [file]
history -w [file]
history --helpOptions:
| Option | Description |
|---|---|
-c |
Clear the in-memory history list |
-a [file] |
Append history entries from this session to file |
-r [file] |
Read file and append its contents to the in-memory history list |
-w [file] |
Write the entire in-memory history list to file, overwriting it |
--help |
Show help information |
If file is not given, $HISTFILE is used.
Behavior:
The history builtin only works in interactive shell mode. With no options, it
prints the current in-memory history list with line numbers. With -c, it
clears the in-memory list. The -a, -r, and -w options append, read, or
write history using the specified file (or $HISTFILE if no file is given). If
-c is combined with one of these options, the history list is cleared before
the file operation is performed. When multiple of -a, -r, or -w are given,
the last one takes effect.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
When the Tab key is pressed, the line editor requests completions for the
word at the cursor (see Tab Completion). The shell then
generates context-aware completions based on the wordβs position within the
command line.
Completions are generated as follows:
-
When completing a command word, completions are generated from executable paths such as
./program, external command names found in the directories listed in$PATH, shell builtins, and aliases. -
When completing an argument or redirection target, completions are generated from file and directory paths. Directory names are returned with a trailing
/to indicate that further path components may follow. -
When the word begins with
$or${, completions are generated from variable names. -
When the word begins with
~, completions are generated from user home directories.
When running interactively in a terminal, xd-shell treats each executed
pipeline as a job for the purpose of job control. In this mode, all processes
belonging to the same job share a process group ID (PGID), and each active job
is assigned a job ID that the shell uses to identify and manage it through
job-control builtins.
xd-shell provides job control only in interactive mode, allowing users to run
jobs in the foreground or background and to suspend or resume them as needed.
In interactive mode, xd-shell manages terminal ownership by deciding which
process group controls the terminal. At any moment, either the shell or the job
running in the foreground holds the terminal, ensuring that input and
terminal-generated signals are delivered correctly.
By default, a job runs in the foreground. The shell gives it control of the
terminal and waits for it to finish or become stopped before reading the next
command. Foreground jobs receive terminal-generated signals such as SIGINT
(Ctrl+C) and SIGTSTP (Ctrl+Z).
A job can also be started in the background by appending & to the command
line. Background jobs do not take control of the terminal; the shell
immediately returns to the prompt so other commands can be entered while the job
continues running. Background jobs do not receive terminal-generated signals,
and if they attempt to read from or write to the terminal, they are stopped by
the operating system through SIGTTIN or SIGTTOU.
βΉοΈ Note: In interactive mode,
xd-shellignores several signals that are intended for foreground jobs rather than for the shell itself, including SIGTERM, SIGINT, SIGQUIT, SIGTSTP, SIGTTIN, and SIGTTOU. This prevents the shell from being interrupted or suspended.
xd-shell tracks the state of each active job so it can report changes and
manage jobs correctly during execution.
A job may be in one of the following states:
- Running β the job is currently executing.
- Stopped β the job has been suspended.
- Done β the job finished with an exit status of
0. - Exited β the job finished with a non-zero exit status.
- Signaled β the job was terminated by a signal.
xd-shell allows referring to jobs using job specifiers, which are short
notations beginning with %. These are used by job-control builtins to identify
which job should be acted on.
Supported job specifiers:
%nβ job with IDn%+or%%β the current job%-β the previous job
After each command completes or a job changes state, xd-shell refreshes the job
table and updates the current and previous jobs. The current job is the most
recently active job, with stopped jobs preferred over running ones. The previous
job is simply the next most recently active job.
The jobs builtin is used to display the status of all active jobs.
Usage:
jobs [-lp]Options:
| Option | Description |
|---|---|
-l |
Show detailed status for each process in the job |
-p |
Show the process ID(s) associated with each job |
--help |
Show help information |
Behavior:
The jobs builtin does not accept any non-option arguments. It prints the
current job table, and the -l and -p options modify which additional
information is included.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The fg builtin is used to move a job into the foreground, resuming it if it is
stopped.
Usage:
fg [job_spec]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
The fg builtin only works when job control is enabled in interactive shell mode.
It accepts at most one optional job_spec operand. If job_spec
is omitted, the current job (as described in Job Specifiers) is
used. The specified job is brought into the foreground, if it was stopped, it is
resumed and runs in the foreground while xd-shell waits for it to finish or stop
again.
Exit status:
Returns the exit status of the command placed in the foreground unless an error occurs.
The bg builtin is used to move one or more jobs to the background, resuming them
if they are stopped.
Usage:
bg [job_spec ...]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
The bg builtin only works when job control is enabled in interactive shell
mode. It accepts zero or more job_spec operands. If no job_spec is given,
the current job (as described in Job Specifiers) is
used. Each specified job is moved to the background, if it was stopped, it is
resumed and continues running in the background as if it had been started with &.
Exit status:
Returns 0 unless job control is not enabled or an error occurs.
The kill builtin is used to send a signal to one or more processes or jobs.
Usage:
kill [-s sigspec | -n signum] pid | jobspec ...
kill -lOptions:
| Option | Description |
|---|---|
-s sig |
Use the signal named by sig |
-n num |
Use the signal numbered num |
-l |
List the available signal names and their numbers |
--help |
Show help information |
Behavior:
By default, kill sends the SIGTERM signal. When -s or -n is provided, the
specified signal name or number determines which signal is sent. Each argument is
either a process ID or a job specifier beginning with % (as described in
Job Specifiers). When a job specifier is used, the signal is
sent to the job's process group. With -l, kill prints the list of available
signals and their numbers instead of sending a signal.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
A shell script is a file containing commands written in the xd-shell language.
Scripts can be executed in two ways: by invoking the shell with a script file as
an argument, or by running an executable script as a normal command.
When the shell is invoked with a script file argument, it reads and executes the
commands in that file sequentially and then exits. Invoking the shell with the
-c option follows the same execution model, reading commands from the provided
string argument and exiting after they are executed.
When an executable script is run as a normal command, the shell locates the
script file using the same rules as for other external commands, as described
in Command Execution. Once the script file is located,
the shell executes it by starting a new xd-shell instance with the script path
passed as an argument. If the script begins with a #! interpreter line, the
specified interpreter may be invoked instead.
βΉοΈ Note: Scripts executed this way do not affect the current shell environment. To execute a script in the current shell environment, use the
sourcebuiltin.
When xd-shell starts, it initializes a small set of common environment
variables and may run commands from a startup file.
A startup file is a normal shell script that is executed in the current shell
process, so variable assignments, exported variables, aliases, and other
changes take effect in the current shell session.
During startup, xd-shell ensures that a basic set of environment variables is
available. These variables provide information commonly expected by shell users
and external programs.
If a variable is already defined, the shell leaves it unchanged. Otherwise, it assigns a default value.
| Variable | Description | Default value (if unset) |
|---|---|---|
HOME |
The current user's home directory | The current user's home directory as defined in the system database |
USER |
The current user's name | The current user's login name as defined in the system database |
LOGNAME |
The current user's login name | The current user's login name as defined in the system database |
PATH |
Directories searched for external commands | /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin |
SHLVL |
Shell nesting level | 1 for login shells; otherwise incremented by 1 |
SHELL |
Path of the running xd-shell executable |
The full path to the xd-shell executable |
HISTFILE |
Persistent command history file | $HOME/.xdsh_history |
xd-shell is considered a login shell when it is invoked with -l, or when
argv[0] begins with a leading - (when started by login).
After completing initialization, xd-shell may run commands from a startup file
to perform user-specified initialization.
If the shell is a login shell, ~/.xdsh_profile is executed. If the shell is an
interactive but non-login shell, ~/.xdshrc is executed.
If the startup file does not exist or cannot be read, it is silently skipped.
xd-shell provides builtins for common shell tasks such as changing the working
directory, printing information, executing commands from a file, and exiting the
shell.
The cd builtin is used to change the shell's current working directory.
Usage:
cd [dir]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
-
Without arguments:
Changes the current directory to$HOME. -
With an argument:
If the argument is-, it is treated as$OLDPWD. Otherwise, it is treated as the target directory.
The cd builtin maintains the environment variables $PWD and $OLDPWD
to track the current and previous working directories.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The pwd builtin is used to print the path of the current working directory.
Usage:
pwdOptions:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
The pwd builtin does not take any arguments. It prints the absolute path of the
current working directory followed by a newline.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The echo builtin is used to write arguments to the standard output.
Usage:
echo [-ne] [arg ...]Options:
| Option | Description |
|---|---|
-n |
Do not output the trailing newline |
-e |
Enable interpretation of backslash escapes |
--help |
Show help information |
Behavior:
The echo builtin prints its arguments separated by a single space. By default,
a trailing newline is printed. When -n is given, the trailing newline is suppressed.
When -e is given, the following backslash escapes are recognized:
| Escape | Meaning |
|---|---|
\a |
Alert (BEL) |
\b |
Backspace |
\c |
Suppress further output |
\e |
Escape character |
\E |
Escape character |
\f |
Form feed |
\n |
New line |
\r |
Carriage return |
\t |
Horizontal tab |
\v |
Vertical tab |
\\ |
Backslash |
If \c is encountered while -e is enabled, echo stops producing output and
does not print a trailing newline.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The source builtin is used to execute commands from a file in the current shell
environment.
Usage:
source fileOptions:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
The source builtin reads commands from the given file and executes them in
the current shell process. The commands are executed in the same mode as the
current shell: if source is used while running a script, the file is executed
as part of the script, and if source is used in interactive mode, the file is
executed as if its commands were entered by the user.
Exit status:
Returns 0 unless an invalid option is given or an error occurs.
The exit builtin is used to exit the shell.
Usage:
exit [n]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
Exits the shell with a status of n. If n is omitted, the exit status is that
of the last command executed.
The logout builtin is used to exit a login shell.
Usage:
logout [n]Options:
| Option | Description |
|---|---|
--help |
Show help information |
Behavior:
Exits the login shell with a status of n. If n is omitted, the exit status is
that of the last command executed. If the shell is not a login shell, it prints
an error and fails.
Contributions are welcome.
Feel free to open an issue for bug reports or suggestions, or submit a pull request
with improvements.
This project is released under the MIT License. See the LICENSE file for details.
Check out xd-readline, a custom command-line editor developed specifically for this project.