JSH is an interactive command interpreter that incorporates a number of classic shell functions, in particular job control, i.e. management of tasks launched from the shell. It can execute all external commands, and offers some internal commands, as well as redirection of standard streams and pipe combinations.
(definition inside src)
The project is first divided into 4 directories, with the main at the top.
The 4 files and their programs are :
builtinsbuiltinscontains alljsh'sinternalcommandprograms. The programs are as follows:pwdwhich used to display the absolute physical reference of the current working directory directory.cdwhich used to change the current working directory to the ref directory (if a valid is a valid reference), the previous working directory if the parameter is -, or $HOME if no parameter is specified.print_last_command_resultwhich used to display the return value of the last command executed.exitwhich used to display a warning message if one or more jobs are running or suspended. The return value is 1 in this case. Otherwise, it terminates thejshprocess with the return value val (if a argument is supplied), or by default the return value of the last command executed.jobswhich, if no argument is given, is used to display the list of current jobs, specifying the job number in square brackets, the process group identifier, the job status (Running, Stopped, Detached, Killed or Done) and the command line it is executing. With the -t option, it lists the process tree for each job, indicating its pid, status and the command it is executing; if a job number is passed as an argument to jobs, the list is restricted to the job in question.bgwhich is used to restart execution of the job specified in the argument in the background.fgwhich is used brings the execution of the job specified in the argument back to the foreground.killwhich is used to send the sig signal (or SIGTERM by default) to all processes of the job number job, or to the process of identifier pid.
parserparser contains the parser of the commands to be executed. The program is as follows:parserwhich parses a command into a structure explained below.
runrun contains the program for executing command lines. The program is as follows:runwhich executes the given line from a line parsed by the parser.
utilsutilscontains cores, and the useful functions needed for strings, for example. The programs is as followsconstantswhich contains all jsh constant variables, as well as the functions to initialize them.corewhich contains the majority of global variables (all except those for jobs) as well as their initialization, update and free functions.int_utilswhich is used to have functions concerning integers.jobs_corewhich contains all global job variables and their related functions.signal_managementwhich is used to manage signal handlers, to remove and reinsert them.string_utilswhich is used to have functions concerning integers.
(definition inside src/utils/core.h)
The core represents the jsh's internal structure, where the project's global variables are located. Its variables are :
current_folder: current user position, initialized withPWDfrom constant.prompt:commandreadout prompt.last_command_exit_value: last user command exit value.last_reference_position: last user location, initialized withPWDfrom constant.last_line_read: last line typed by the user.current_pipeline_list: current_pipeline run.
(definition inside src/utils/jobs_core.h)
The core represents the jsh's internal jobs structure, where the project's global jobs variables are located. Its variables are :
job_number: the number ofjobmonitored byjsh.jobs: thejoblist, monitored byjsh.
(definition inside src/parser/parser.h)
One of the main features of jsh is its parsing management.
To achieve this, we use several structures to represent a command line:
-
pipeline_list (contains
pipelinesstructure) It is used to delimit&and thus what is considered a futurejobor not. Eachpipelineis then considered as a line in its own right. -
pipeline (contains
commandstructures and a booleanto_job) This structure is used to delimit the|betweencommands. Theto_jobboolean is used to determine whether or not thepipelineshould be monitored during execution. -
command (contains a
name,argumentstructures andredirectstructures) This structure represents acommandwith its name and arguments, which can be substitutions, and which may or may not containredirects. -
command_without_substitution (contains a
name,argumentstrings,redirectsstructures and thepidsof these substitutions) This structure represents acommandwith its name and arguments. Unlike acommand, there is no substitution in the arguments, which have been replaced by thecommandpipe. After waiting for the end of these substitutions with theirpids, thecommandis now ready to be executed. -
argument (contains a
typeand avalue) This structure is represented by itstypeto determine whether it is a substitution or not. Thevaluewill contain thecommandstring and the substitutionpipeline. -
redirection (contains a
type, amodeand afile's reference string) Theredirection typeis used to determine which descriptor fromstandard input,standard outputorerror outputshould be redirected to theredirection file. Themoderepresents theredirection file'sopening modes, includingoverwrite,append,no overwriteandnone.
(definition inside src/utils/jobs_core.h)
Another important part of jsh is its job management. For this, 2 different structures are used:
-
job (contains an
id, apgid, thepidof the leader of the group, astatus, apipelineand itsprocesses) The structure is represented by theid, which differentiates thejobfrom otherjobs, and thepgidand pid of the leader. The job's pipeline represents the line that created it, its status is an enumeration ofRunning,Stopped,Deprecated,KilledandDone, and finally itsprocessesstructures. This whole structure represents a group ofprocesses, monitored byjsh. -
process (contains a
pid, astatus, acommandand acommand_without_substitution) The structure is represented by thepid, which differentiates theprocessfrom others. Theprocess's commandrepresent the executedcommandof it, and itsstatusis the same enumeration asjob. Aprocessis used to represent the various forks created byjshthat are monitored by thejobcreated.
A given pipeline is part of the same job if it is not separated from the previous one by a &. Each processes part of this job shares the same process group identifier (PGID), which is the PID of the first process in the pipeline. The PGID is used to send signals to all processes in the job.
cat <( echo grosminet | cat ) <( echo titi | cat | cat ) <( ls -l > redirected.txt ) | wc -l > 2.txt This bash command is an interesting combination of several elements: process substitutions, redirections, pipes, and commands.
-
Process substitution with
<():<(...): This syntax creates what's called a "substitution," allowing you to execute a command as if it were a file. Here, you have three subshells:<( echo grosminet | cat ): This substitution runsecho grosminetand then passes its output tocat, which simply displays it.<( echo titi | cat | cat ): Similarly,echo titiis passed tocatand then to anothercat.<( ls -l > redirected.txt ): This substitution lists the contents of the current directory with details (ls -l), but instead of displaying the result, it redirects the output to the fileredirected.txt.
-
Concatenation of outputs with
cat:- The main
catcommand (at the beginning of the line) takes the outputs of the three substitutions and concatenates them. Since the third substitution redirects its output to a file, it doesn't add anything to thecatoutput.
- The main
-
Piping to
wc -l:| wc -l: The result ofcat(which is now the combination of outputs from "grosminet" and "titi") is piped towc -l, which counts the number of lines.
-
Final redirection to
2.txt:> 2.txt: The output ofwc -l(the line count) is then redirected to a file named2.txt.
Once the command is entered, it is parsed by the parser, which will create a pipeline_list structure. This structure will contain one pipeline structure which will contain two command structures separated by |. The first command structure will contain three argument structures, which are all of type substitution structure. The second command structure will contain one argument structure, of type string and one redirect structure. The redirection structure will contain the RedirectionType REDIRECT_STDOUT, the RedirectionMode REDIRECT_NO_OVERWRITE and the file's name 2.txt.
After the parsing stage, the run module takes over. This module is responsible for executing the parsed command structures.
- Executing Pipeline :
run_pipeline_list is the function that executes the pipeline list. It takes the pipeline list structure as an argument and executes each pipeline in the list using run_pipeline. In this case, there is only one pipeline in the list, so it is executed directly.
In run_commands_of_pipeline, the STDOUT of the first command is redirected to the write end of the pipe, and the STDIN of the second command is redirected to the read end of the pipe. This allows the output of the first command to be used as the input for the second command.
- Executing Substitutions:
For each substitution structure in the command structure, the run module executes the command inside the substitution. This is done by fd_from_subtitution_arg_with_pipe by creating a new process using fork(), executing the command in the child process, and capturing the output in the parent process. The output is then used as an argument for the main command.
- Handling Redirections:
If there are any redirections in the command structure, the run_command handles them before executing the command. For output redirections (like > 2.txt), the run module changes the file descriptor of STDOUT to point to the specified file. This means that any output from the command will be written to the file instead of the terminal.
- Executing the Commands:
The run module then executes the first command in the pipeline using run_command_without_redirections. If the command is a builtin (such as pwd or ?), it is executed directly in the current process. If it is an external command, a new process is created using fork(), and the command is executed in the child process. The input and output of the command are redirected to the read and write ends of the pipe, respectively. This allows the output of the command to be used as the input for the next command in the pipeline.
sleep 3 & sleep 2 & sleep 5 The sleep 3 & sleep 2 & sleep 5 command in Bash utilizes a combination of sleep and the & character. Here's what happens:
sleep 3: This command instructs the shell to pause for 3 seconds.&: The&symbol at the end of a command in a Bash shell means that the command should be executed in the background. This allows the shell to continue running other commands without waiting for thesleepcommand to finish.sleep 2: Right after that, thesleep 2command is launched. It instructs the shell to pause for 2 seconds. Like the previous command, it's executed in the background.sleep 5: Finally, thesleep 5command is executed, requesting a pause of 5 seconds.
All these sleep commands are executed almost simultaneously in the background, except for the last one. They do not block each other. The shell does not wait for each sleep to finish before moving on to the next one.
So, even though the sleep 3 and sleep 2 commands complete after 3 and 2 seconds, respectively, the sleep 5 command continues running in the foreground, and the shell waits for it to finish before giving you back control.
After parsing, the pipeline_list structure will contain three pipeline structures, each containing one command structure.
-
When the command
sleep 3 & sleep 2 & sleep 5is run, therun_pipeline_listwill start three separate processes for each sleep command. Because of the & symbol, the first two sleep commands will be run in the background, allowing the shell to immediately start the next command. -
When a sleep command is started in the background, a job is created with
init_job_to_addand added to the jobs list using theadd_job_to_jobsfunction fromsrc/utils/jobs_core.c. The shell can then continue executing other commands without waiting for these background tasks to finish. -
When the last sleep command is started, the shell will wait for it to finish before giving you back control. This is because the last sleep command is not started in the background, so the shell will wait for it to finish before continuing.