[Linux exercise] simulate a simple shell

Time:2022-5-3


1、 Foreword

Shell is a command-line interpreter. Its function is to give the command to bash to execute, and Bash gives the task to the sub process to complete, so that bash will not be affected even if there is a problem with the task.

The subprocess does not directly execute the task, but is completed through program replacement, so the two most important parts of shell simulation are creating subprocess and program replacement.

Create child process inProcess conceptAs mentioned in, let’s focus on process replacement first, and then simulate the implementation of shell.


2、 Process program replacement

1. Replacement principle

After creating a child process with fork, it executes the same program as the parent process (but it may execute different code branches). The child process often calls an exec function to execute another program. When a process calls an exec function, the user space code and data of the process are completely replaced by the new program and executed from the start routine of the new program.Calling exec does not create a new process, so the ID of the process does not change before and after calling exec.


2. Substitution function

[Linux exercise] simulate a simple shell
All functions begin with exec, and the following letters can be remembered with the following understanding
L (list): indicates that the parameter list is adopted
V (vector): array for parameters
P (path): indicates the path to search the program in the environment variable
E (Env): indicates the user-defined environment variable in the parameter

It can be seen from the above that there is not much difference between the use of these six functions. Take EXECL as an example to explain in detail.


int execl(const char *path, const char *arg, …);

Path is the path of the program, how arg is executed, and “…” is a variable parameter, which must end with null. Here is an example.

First view the path to LS.
[Linux exercise] simulate a simple shell

#include <stdio.h>
#include <unistd.h>

int main()
{
	printf("I am a process!\n");
	sleep(3);
	// 		 route 		   ls 		 Command line parameters 	 Null terminated
	execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
	return 0;
}

The result of the final program is obviously the same as that of running LS – A – I – L in the dynamic diagram.
[Linux exercise] simulate a simple shell

In this way, other programs (such as LS) can be called in the myproc process I wrote.


Process replacement will not create a new process, but directly replace the original code and data with new code and data. Therefore, the code after process replacement will not be executed and will be replaced directly.

#include <stdio.h>
#include <unistd.h>

int main()
{
	printf("I am a process!\n");
	sleep(3);
	execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
	printf("you can't see me!\n");// See if this code is executed
	return 0;
}

You can’t see me!, Therefore, the above conclusion can be verified.
[Linux exercise] simulate a simple shell


Program replacement may also fail. Let’s take the path error as an example to see the results.

#include <stdio.h>
#include <unistd.h>

int main()
{
	printf("I am a process!\n");
	sleep(3);
	//The correct path here is usr / bin / LS
	execl("/us/bin/ls", "ls", "-a", "-i", "-l", NULL);
	printf("you can't see me!\n");// See if this code is executed
	return 0;
}

[Linux exercise] simulate a simple shell
Because the process replacement fails, the following code and data will not be replaced. Continue to run the following code.

SoIn terms of use, the functions of exec series do not need to judge the return value. As long as they return, they fail.


Generally, the functions of exec series are called to create sub processes, and then replace the code and data of sub processes to execute other programs. The function is the same as the above code. The new code is as follows.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
	pid_t id = fork();
	if (id == 0)
	{
		printf("I am a process!\n");
		sleep(3);
		execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);
		exit(10);
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if (ret > 0 && WIFEXITED(status))
	{
		printf("signal:%d\n", WIFEXITED(status));
		printf("exit code:%d\n", WEXITSTATUS(status));
	}
	return 0;
}

[Linux exercise] simulate a simple shell


Here are some equivalent expressions of functions that do not require environment variables.
[Linux exercise] simulate a simple shell


3、 Simulate a simple shell

First, take LS as an example to illustrate the general process of shell.

Use the timeline in the figure below to represent the sequence of events. The time is from left to right. The shell is represented by a square marked sh, which moves from left to right over time.
[Linux exercise] simulate a simple shell

The shell reads the character “LS” from the user, then creates a new sub process, then runs the LS program in the newly created process and waits for the process to end.

Then the shell reads a new line of input, creates a new process, runs the program in this process, and waits for the process to end.

Therefore, to write a shell, you need to cycle the following process:

  1. Get command line
  2. Parse command line
  3. Establish a child process (fork)
  4. Replace child process (exec)
  5. The parent process waits for the child process to exit (wait)

1.gethostname

First, there is the following prompt in front of the command line:
[Linux exercise] simulate a simple shell
These contents can be obtained through gethostname.
[Linux exercise] simulate a simple shell

Obtain the host name with the following code:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
	char name[100];
	while (1)
	{
		gethostname(name, sizeof(name));
		printf("%s\n", name);
	}
	return 0;
}

[Linux exercise] simulate a simple shell
In the following code, I directly use my own fictional host name to represent the simulated shell.


2. Simulation Implementation

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>

#Define len 1024 // the maximum number of characters read is 1024
#Define num 32 // Command + command line parameters up to 32

int main()
{
	char cmd[LEN];
	while (1)
	{
		//Print prompt
		printf("[[email protected]_centos dir]& ");

		//Get user input (actually a string)
		fgets(cmd, LEN, stdin);// Read the string entered by the user from standard input (usually keyboard)
		cmd[strlen(cmd) - 1] = '
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#Define len 1024 // the maximum number of characters read is 1024
#Define num 32 // Command + command line parameters up to 32
int main()
{
char cmd[LEN];
while (1)
{
//Print prompt
printf("[[email protected]_centos dir]& ");
//Get user input (actually a string)
fgets(cmd, LEN, stdin);// Read the string entered by the user from standard input (usually keyboard)
cmd[strlen(cmd) - 1] = '\0';// The last position was originally '\ n'
//Parse string
char* myArg[NUM];
//The input string has at least the first command
myArg[0] = strtok(cmd, " ");// Split the command first with a space as the delimiter
int i = 1;
//Get the command line parameters
While (myarg [i] = strtok (null, "") // it is extracted from the previous substring by default, so null is passed
{
i++;
}
//Let subprocesses execute commands
pid_t id = fork();
if (id == 0)
{
//child
execvp(myArg[0], myArg);
exit(11);
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0 && WIFEXITED(status))
{
printf("signal:%d,", WIFEXITED(status));
printf("exit code:%d\n", WEXITSTATUS(status));
}
}
return 0;
}
';// The last position was originally '\ n' //Parse string char* myArg[NUM]; //The input string has at least the first command myArg[0] = strtok(cmd, " ");// Split the command first with a space as the delimiter int i = 1; //Get the command line parameters While (myarg [i] = strtok (null, "") // it is extracted from the previous substring by default, so null is passed { i++; } //Let subprocesses execute commands pid_t id = fork(); if (id == 0) { //child execvp(myArg[0], myArg); exit(11); } int status = 0; pid_t ret = waitpid(id, &status, 0); if (ret > 0 && WIFEXITED(status)) { printf("signal:%d,", WIFEXITED(status)); printf("exit code:%d\n", WEXITSTATUS(status)); } } return 0; }

[Linux exercise] simulate a simple shell


But there are also some commands that cannot be implemented, such as using ‘‘ Separate two commands, when using ‘|’ pipeline, CD and other commands, some are because they are not considered when processing strings in the program, and some are because the command itself is not completed by fork.

The simulated shell implementation here is just to replace fork and process, so it will inevitably have defects.


Thank you for reading. Please criticize and correct any mistakes

Recommended Today

Usage Summary of Python collaborative process (I)

1、 Python collaboration keyword Async: declare collaboration Note: the function declared through async is not a function, and a separate call will not be executed Await: it is used to wait for objects that can wait, including collaboration (that is, the collaboration declared by async), task (asyncio. Create_task(), asyncio ensure_ Future () is the task […]