Specific Code Implementation of shell with basic functions

Source: Internet
Author: User

Specific Code Implementation of shell with basic functions

I have been interacting with shell for the last few months, and I feel shell is full of mystery. I accidentally saw an article explaining the implementation of shell, and it was not very difficult, so I began to develop my own minishell, by the way, it also reinforces the knowledge of linux programming that I learned some time ago.

First, let's take a look at the functions implemented by my minishell:

1. Supports External commands such as ls, touch, and wc.
2. Support for input and output redirection characters

3. Support for pipeline commands

4. Support for background jobs

5. Supports Internal commands such as cd, jobs, kill, and exit (You have also written an about command ^ _ ^)

6. Support for ctrl + c and ctrl + z Signal Processing

Next, we will analyze them one by one according to the writing steps:

(1) Command Parsing

Parsing input commands accounts for a large proportion in this program, although such a program for parsing Common commands (regular expressions are too difficult ..) The interpreter is not difficult, but the robustness and comprehensiveness still need to be fully considered.
Here we use segment resolution, first remove the starting space, tabs, and so on, and use this and some '|', '<' as the Division boundary to parse the COMMAND to the COMMAND structure. Let's look at the code. The comments are very detailed!

[Cpp] view plaincopy
  1. /*
  2. * Parsing command
  3. * If the number of parsed commands is returned successfully,-1 is returned if the command fails to be parsed.
  4. */
  5. Intparse_command (void)
  6. {
  7. /* Cat <test.txt | grep-npublic> test2.txt &*/
  8. If (check ("\ n "))
  9. Return0;
  10. /* Determine whether the command is internal and execute it */
  11. If (builtin ())
  12. Return0;
  13. /* 1. parse the first simple command */
  14. Get_command (0 );
  15. /* 2. Determine whether there is an input redirection character */
  16. If (check ("<"))
  17. Getname (infile );
  18. /* 3. Determine whether a media transcoding queue exists */
  19. Inti;
  20. For (I = 1; I <PIPELINE; ++ I)
  21. {
  22. If (check ("| "))
  23. Get_command (I );
  24. Else
  25. Break;
  26. }
  27. /* 4. determine whether an output redirection character exists */
  28. If (check ("> "))
  29. {
  30. If (check ("> "))
  31. Append = 1;
  32. Getname (outfile );
  33. }
  34. /* 5. Determine whether the background job is used */
  35. If (check ("&"))
  36. Backgnd = 1;
  37. /* 6. Determine whether the command ends '\ n '*/
  38. If (check ("\ n "))
  39. {
  40. Pai_count = I;
  41. Return1__count;
  42. }
  43. Else
  44. {
  45. Fprintf (stderr, "Commandlinesyntaxerror \ n ");
  46. Return-1;
  47. }
  48. }
  49. /*
  50. * Parse simple commands to cmd [I]
  51. * Extract the command parameters in cmdline to the avline array,
  52. * And point each pointer in the args [] in the COMMAND structure to these strings
  53. */
  54. Voidget_command (inti)
  55. {
  56. /* Cat <test.txt | grep-npublic> test2.txt &*/
  57. Intj = 0;
  58. Intinword;
  59. While (* lineptr! = '\ 0 ')
  60. {
  61. /* Remove Spaces */
  62. While (* lineptr = ''| * lineptr = '\ t ')
  63. Lineptr ++;
  64. /* Point the j parameter of the I command to avptr */
  65. Cmd [I]. args [j] = avptr;
  66. /* Extract parameters */
  67. While (* lineptr! = '\ 0'
  68. & * Lineptr! =''
  69. & * Lineptr! = '\ T'
  70. & * Lineptr! = '>'
  71. & * Lineptr! = '<'
  72. & * Lineptr! = '|'
  73. & * Lineptr! = '&'
  74. & * Lineptr! = '\ N ')
  75. {
  76. /* Extract the parameter to the avline array directed by the avptr pointer */
  77. * Avptr ++ = * lineptr ++;
  78. Inword = 1;
  79. }
  80. * Avptr ++ = '\ 0 ';
  81. Switch (* lineptr)
  82. {
  83. Case '':
  84. Case '\ t ':
  85. Inword = 0;
  86. J ++;
  87. Break;
  88. Case '<':
  89. Case '> ':
  90. Case '| ':
  91. Case '&':
  92. Case '\ N ':
  93. If (inword = 0)
  94. Cmd [I]. args [j] = NULL;
  95. Return;
  96. Default:/* for '\ 0 '*/
  97. Return;
  98. }
  99. }
  100. }
  101. /*
  102. * Match the string in lineptr with str
  103. * 1 is returned. lineptr is removed from the matched string.
  104. * 0 is returned for failure, and lineptr remains unchanged.
  105. */
  106. Intcheck (constchar * str)
  107. {
  108. Char * p;
  109. While (* lineptr = ''| * lineptr = '\ t ')
  110. Lineptr ++;
  111. P = lineptr;
  112. While (* str! = '\ 0' & * str = * p)
  113. {
  114. Str ++;
  115. P ++;
  116. }
  117. If (* str = '\ 0 ')
  118. {
  119. Lineptr = p;/* lineptr shifts the matched string */
  120. Return1;
  121. }
  122. /* Keep lineptr unchanged */
  123. Return0;
  124. }
  125. Voidgetname (char * name)
  126. {
  127. While (* lineptr = ''| * lineptr = '\ t ')
  128. Lineptr ++;
  129. While (* lineptr! = '\ 0'
  130. & * Lineptr! =''
  131. & * Lineptr! = '\ T'
  132. & * Lineptr! = '>'
  133. & * Lineptr! = '<'
  134. & * Lineptr! = '|'
  135. & * Lineptr! = '&'
  136. & * Lineptr! = '\ N ')
  137. {
  138. * Name ++ = * lineptr ++;
  139. }
  140. * Name = '\ 0 ';
  141. }

(2) Command Execution and Implementation

1. Program Framework:

After the command is parsed, let's consider two major directions: External commands or internal commands?

For external commands, we only need to fork a sub-process and execvp () for execution. For internal commands, we need to implement them by ourselves.

Two questions are raised: first, why execvp ()? Second, why do we need to fork a sub-process to implement it? Isn't a direct while loop possible?

Answer:

(1) We use execvp () because the prototype of the function is int execvp (const char * file, char * const argv []). The first parameter is the command file name, the second is the parameter, which is inconvenient to execute commands.

(2) Once execvp () is executed, the current process will be replaced by the execvp process, and the program will end after execution. Therefore, the while loop is not feasible, you must fork a sub-process for execution.

[Cpp] view plaincopy
  1. While (1) {/* repeatforever */
  2. Type_prompt ();/* displaypromptonthescreen */
  3. Read_command (command, parameters);/* readinputfromterminal */
  4. If (fork ()! = 0) {/* forkoffchildprocess */
  5. /* Parentcode */
  6. Waitpid (-1, & status, 0);/* waitforchildtoexit */
  7. } Else {
  8. /* Childcode */
  9. Execvp (command, parameters);/* executecommand */
  10. }
  11. }

Using this framework, the functions of External commands (executable files) can be basically implemented (vi, top, ps, etc ).

2. Input/Output redirection

When the analysis shows that there is a redirection symbol of input and output, we need to use the dup () function. For more information about functions, see my blog.

We use a struct to save the input syntactic analysis results:

[Cpp] view plaincopy
  1. Typedefstructcommand
  2. {
  3. Char * args [MAXARG + 1];/* List of parsed command parameters */
  4. Intinfd;
  5. Intoutfd;
  6. } COMMAND;
Basic Process:

[Cpp] view plaincopy
  1. /* Sub-process */
  2. If (cmd [I]. infd! = 0)
  3. {
  4. Close (0 );
  5. Dup (cmd [I]. infd );
  6. }
  7. If (cmd [I]. outfd! = 1)
  8. {
  9. Close (1 );
  10. Dup (cmd [I]. outfd );
  11. }
  12. Intj;
  13. For (j = 3; j <OPEN_MAX; ++ j)
  14. Close (j );
Cmd [I]. infd and cmd [I]. outfd are the global variables of the resolved redirection location.

3. Pipeline commands

Pipeline commands are implemented using the pipe () function. For more information about pipelines, see my blog.

If we have a | B | c command, we need to create two pipelines, and so on.

[Cpp] view plaincopy
  1. Inti;
  2. Intfd;
  3. Intfds [2];
  4. For (I = 0; I <distinct _count; ++ I)
  5. {
  6. /* If it is not the last command, create an MPS queue */
  7. If (I <cmd_count-1)
  8. {
  9. Pipe (fds );
  10. Cmd [I]. outfd = fds [1];
  11. Cmd [I + 1]. infd = fds [0];
  12. }
  13. Forkexec (I );
  14. If (fd = cmd [I]. infd )! = 0)
  15. Close (fd );
  16. If (fd = cmd [I]. outfd )! = 1)
  17. Close (fd );
  18. }
  19. If (backgnd = 0)
  20. {
  21. /* Foreground job. Wait for the last command in the MPs queue to exit */
  22. While (wait (NULL )! = Lastpid)
  23. ;
  24. }

4. background jobs and signal processing

To judge the background, we only need to parse the command to check whether "&" exists. If yes, backgnd = 1 and no wait is performed on background processes. To avoid zombie processes, we chose to use signal () to process SIGCHLD, ignore it, and ignore SIGINT and SIGQUIT signals (the background does not respond to ctrl + c, ctrl + z ). However, when backgnd is set to 0, you need to set the two signals to the default processing. Otherwise, the front-end cannot respond to the signals.


5. Internal commands

1. cd command implementation
The implementation of the cd command mainly depends on the System Call chdir (). We can pass the first parameter to chdir to make a successful cd call. By determining the different return values of chdir (), you can determine whether the directory is successfully changed and the cause of the error can be output.

[Cpp] view plaincopy
  1. Voiddo_cd (void)
  2. {
  3. Get_command (0 );
  4. Intfd;
  5. Fd = open (* (cmd [0]. args), O_RDONLY );
  6. Fchdir (fd );
  7. Close (fd );
  8. }

2. Implementation of the jobs command
The jobs Command maintains a linked list. Each time a background process is run, a data table must be added to the linked list. When the child process ends, it will send a SIGCHLD signal to the parent process. The parent process, that is, Shell, needs to process the signal and processes the corresponding processes in the linked list of the background process, that is, remove it.

[Cpp] view plaincopy
  1. /* Parent process */
  2. If (backgnd = 1)
  3. {
  4. /* Add the linked list of jobs */
  5. NODE * p = (NODE *) malloc (sizeof (NODE ));
  6. P-> npid = pid;
  7. Printf ("% s", cmd [0]. args [0]);
  8. Strcpy (p-> backcn, cmd [0]. args [0]);
  9. // Printf ("% s", p-> backcn );
  10. NODE * tmp = head-> next;
  11. Head-> next = p;
  12. P-> next = tmp;
  13. }

3. Implementation of the exit command
The exit command is implemented in two parts. First, you can directly call the system to call exit () when the lexical analysis reaches exit. Second, before exiting, determine whether there are still unfinished tasks in the Linked List of background processes. If there are unfinished tasks, notify the user and wait for the user to select.

[Cpp] view plaincopy
  1. Voiddo_exit (void)
  2. {
  3. IntPgnum = 0;
  4. NODE * tmp = head-> next;
  5. While (tmp! = NULL)
  6. {
  7. Pgnum ++;
  8. Tmp = tmp-> next;
  9. }
  10. If (Pgnum! = 0)
  11. {
  12. Printf ("Thereareprogramsinthebackground, areyousuretoexit? Y/N \ n ");
  13. Charc = getchar ();
  14. If (c = 'n ')
  15. Return;
  16. Else
  17. Gotoloop;
  18. }
  19. Loop:
  20. Printf ("exit \ n ");
  21. Exit (EXIT_SUCCESS );
  22. }
4. kill command implementation
The implementation of the kill command is implemented by signal. We use kill-9 + pid to force the end Of the background process, use a kill system call to send a SIGQUIT signal to the corresponding process to force the process to exit.

[Cpp] view plaincopy
  1. Voiddo_kill (void)
  2. {
  3. Get_command (0 );
  4. Intnum = atoi (cmd [0]. args [1]);
  5. Signal (SIGQUIT, SIG_DFL );
  6. Kill (num, SIGQUIT );
  7. Signal (SIGQUIT, SIG_IGN );
  8. NODE * bng = head-> next;
  9. NODE * pre = head;
  10. While (bng! = NULL)
  11. {
  12. If (bng-> npid = num)
  13. {
  14. NODE * nxt = bng-> next;
  15. Pre-> next = nxt;
  16. Break;
  17. }
  18. Pre = bng;
  19. Bng = bng-> next;
  20. }
  21. }

By now, the functions of this program have been basically implemented, and the results are quite good.

Note: The specific source code of this program is hosted on Github. Thank you for your attention!

However, there are still some shortcomings:

1. Due to the lack of time and test, there must be bugs.

2. Unable to support complex command parsing such as regular expressions

3. shell scripts cannot be executed.

4. The upper/lower key Viewing History command is not enabled.

In general, I have gained a lot and hope to help you!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.