设为首页 收藏本站
查看: 607|回复: 0

CSAPP2e:Shell lab 解答

[复制链接]

尚未签到

发表于 2015-12-3 14:23:43 | 显示全部楼层 |阅读模式
          期中之后的第一个lab 就是实现一个简单的Shell 程序,程序的大部分已经写好,只需要实现 eval 函数和处理信号的sigchld_handle, sigint_handle, sigtstp_handle这三个函数。 这个lab 主要要求处理好各个信号,因为上课的时候一直听得很糊涂,就拖着没有写,直到这两天deadline逼近才动手。同样是时间紧迫,debug的时候出了很多问题,在网上搜了很多解答,但是因为题目版本不一样,并不完全适用,比如之前的不需要重定向。因此把自己写的代码也贴出来,最后是一些自己的心得。


        这里还有shell lab的所有文件压缩包提供下载




        一些心得:




  • 测试的时候的一些奇葩函数, mytstpp 向shell 发出一个SIGTSTP,而mytstps 向自己的进程发出SIGTSTP信号。前者shell 会调用sigtstp_handle 函数,而后者会使子进程stop,然后向shell 发出SIGCHLD信号,shell调用sigchld_handle 函数。这就要求我们分清楚每一个信号会由哪个进程(函数)处理。
  • shell收到的每个SIGTDTP,SIGINT信号都要发给前台进程,而这个前台进程是由自己的job_list 列表维护的,而实际上每个子进程的停止,终止都是由信号操作。
  • 调试的时候可以用GDB,但是涉及到信号和线程,可以在网上搜一下有很好的教程。
  • 通过运行tshref 可以找到每个命令的标准输出,这个稍微注意一下就可以了。


就写到这里了,如果有错误,烦请指正,同时欢迎交流!



代码附下:




  1 /*
  2  * tsh - A tiny shell program with job control
  3  *
  4  * 2014.12.1
  5  *
  6  */
  7 #include <assert.h>
  8 #include <stdio.h>
  9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <signal.h>
14 #include <sys/types.h>
15 #include <fcntl.h>
16 #include <sys/wait.h>
17 #include <errno.h>
18
19 /* Misc manifest constants */
20 #define MAXLINE    1024   /* max line size */
21 #define MAXARGS     128   /* max args on a command line */
22 #define MAXJOBS      16   /* max jobs at any point in time */
23 #define MAXJID    1<<16   /* max job ID */
24
25 /* Job states */
26 #define UNDEF         0   /* undefined */
27 #define FG            1   /* running in foreground */
28 #define BG            2   /* running in background */
29 #define ST            3   /* stopped */
30
31 /*
32  * Jobs states: FG (foreground), BG (background), ST (stopped)
33  * Job state transitions and enabling actions:
34  *     FG -> ST  : ctrl-z
35  *     ST -> FG  : fg command
36  *     ST -> BG  : bg command
37  *     BG -> FG  : fg command
38  * At most 1 job can be in the FG state.
39  */
40
41 /* Parsing states */
42 #define ST_NORMAL   0x0   /* next token is an argument */
43 #define ST_INFILE   0x1   /* next token is the input file */
44 #define ST_OUTFILE  0x2   /* next token is the output file */
45
46 /* Global variables */
47 extern char **environ; /* defined in libc */
48 char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
49 int verbose = 0; /* if true, print additional output */
50 int nextjid = 1; /* next job ID to allocate */
51 char sbuf[MAXLINE]; /* for composing sprintf messages */
52
53 struct job_t { /* The job struct */
54     pid_t pid; /* job PID */
55     int jid; /* job ID [1, 2, ...] */
56     int state; /* UNDEF, BG, FG, or ST */
57     char cmdline[MAXLINE]; /* command line */
58 };
59 struct job_t job_list[MAXJOBS]; /* The job list */
60
61 struct cmdline_tokens {
62     int argc; /* Number of arguments */
63     char *argv[MAXARGS]; /* The arguments list */
64     char *infile; /* The input file */
65     char *outfile; /* The output file */
66     enum builtins_t { /* Indicates if argv[0] is a builtin command */
67         BUILTIN_NONE, BUILTIN_QUIT, BUILTIN_JOBS, BUILTIN_BG, BUILTIN_FG
68     } builtins;
69 };
70 /* End global variables */
71
72 /* Function prototypes */
73 void eval(char *cmdline);
74
75 void sigchld_handler(int sig);
76 void sigtstp_handler(int sig);
77 void sigint_handler(int sig);
78
79 /* Here are helper routines that we've provided for you */
80 int parseline(const char *cmdline, struct cmdline_tokens *tok);
81 void sigquit_handler(int sig);
82
83 void clearjob(struct job_t *job);
84 void initjobs(struct job_t *job_list);
85 int maxjid(struct job_t *job_list);
86 int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline);
87 int deletejob(struct job_t *job_list, pid_t pid);
88 pid_t fgpid(struct job_t *job_list);
89 struct job_t *getjobpid(struct job_t *job_list, pid_t pid);
90 struct job_t *getjobjid(struct job_t *job_list, int jid);
91 int pid2jid(pid_t pid);
92 void listjobs(struct job_t *job_list, int output_fd);
93
94 void usage(void);
95 void unix_error(char *msg);
96 void app_error(char *msg);
97 typedef void handler_t(int);
98 handler_t *Signal(int signum, handler_t *handler);
99
100 /*
101  * main - The shell's main routine
102  */
103 int main(int argc, char **argv) {
104     char c;
105     char cmdline[MAXLINE]; /* cmdline for fgets */
106     int emit_prompt = 1; /* emit prompt (default) */
107
108     /* Redirect stderr to stdout (so that driver will get all output
109      * on the pipe connected to stdout) */
110     dup2(1, 2);
111
112     /* Parse the command line */
113     while ((c = getopt(argc, argv, "hvp")) != EOF) {
114         switch (c) {
115         case 'h': /* print help message */
116             usage();
117             break;
118         case 'v': /* emit additional diagnostic info */
119             verbose = 1;
120             break;
121         case 'p': /* don't print a prompt */
122             emit_prompt = 0; /* handy for automatic testing */
123             break;
124         default:
125             usage();
126         }
127     }
128
129     /* Install the signal handlers */
130
131     /* These are the ones you will need to implement */
132     Signal(SIGINT, sigint_handler); /* ctrl-c */
133     Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
134     Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
135     Signal(SIGTTIN, SIG_IGN);
136     Signal(SIGTTOU, SIG_IGN);
137
138     /* This one provides a clean way to kill the shell */
139     Signal(SIGQUIT, sigquit_handler);
140
141     /* Initialize the job list */
142     initjobs(job_list);
143
144     /* Execute the shell's read/eval loop */
145     while (1) {
146
147         if (emit_prompt) {
148             printf("%s", prompt);
149             fflush(stdout);
150         }
151         if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
152             app_error("fgets error");
153         if (feof(stdin)) {
154             /* End of file (ctrl-d) */
155             printf("\n");
156             fflush(stdout);
157             fflush(stderr);
158             exit(0);
159         }
160
161         /* Remove the trailing newline */
162         cmdline[strlen(cmdline) - 1] = '\0';
163
164         /* Evaluate the command line */
165         eval(cmdline);
166
167         fflush(stdout);
168         fflush(stdout);
169     }
170
171     exit(0); /* control never reaches here */
172 }
173
174 /*
175  * eval - Evaluate the command line that the user has just typed in
176  *
177  * If the user has requested a built-in command (quit, jobs, bg or fg)
178  * then execute it immediately. Otherwise, fork a child process and
179  * run the job in the context of the child. If the job is running in
180  * the foreground, wait for it to terminate and then return.  Note:
181  * each child process must have a unique process group ID so that our
182  * background children don't receive SIGINT (SIGTSTP) from the kernel
183  * when we type ctrl-c (ctrl-z) at the keyboard.
184  */
185 void eval(char *cmdline) {
186     int bg; /* should the job run in bg or fg? */
187     struct cmdline_tokens tok;
188
189     /* Parse command line */
190     bg = parseline(cmdline, &tok);
191
192     if (bg == -1)
193         return; /* parsing error */
194     if (tok.argv[0] == NULL)
195         return; /* ignore empty lines */
196
197     int stdi,stdo;
198     stdi=dup(STDIN_FILENO);
199     stdo=dup(STDOUT_FILENO);
200
201     int infg, outfg;
202     infg = -1;
203     outfg = -1;
204     if (tok.infile != NULL) {
205         infg = open(tok.infile, O_RDONLY, 0);
206         dup2(infg, STDIN_FILENO);
207     }
208     if (tok.outfile != NULL) {
209         outfg = open(tok.outfile, O_RDWR, 0);
210         dup2(outfg, STDOUT_FILENO);
211     }
212
213     pid_t pid;
214     struct job_t *job;
215     sigset_t mask;
216     sigemptyset(&mask);
217     sigaddset(&mask, SIGCHLD);
218     sigaddset(&mask, SIGINT);
219     sigaddset(&mask, SIGTSTP);
220     if (tok.builtins == BUILTIN_NONE) {
221         sigprocmask(SIG_BLOCK, &mask, NULL);
222
223         if ((pid = fork()) == 0) {
224             sigprocmask(SIG_UNBLOCK, &mask, NULL);
225             setpgid(0, 0);
229
230             execve(tok.argv[0], tok.argv, environ);
231
232             if(infg!=-1)
233                 close(infg);
234             if(outfg!=-1)
235                 close(outfg);
236
237         } else {
238
239             addjob(job_list, pid, bg + 1, cmdline);
240             job = getjobpid(job_list, pid);
241             sigprocmask(SIG_UNBLOCK, &mask, NULL);
242
243             sigemptyset(&mask);
244             if (!bg) {
245                 while(pid==fgpid(job_list))
246                     sleep(0);
247             } else {
248                 printf("[%d] (%d) %s\n", job->jid, pid, job->cmdline);
249             }
250         }
251
252     } else {
253         if(tok.builtins==BUILTIN_QUIT)
254             exit(0);
255         else if(tok.builtins==BUILTIN_JOBS) {
256             listjobs(job_list,STDOUT_FILENO);
257         }
258         else{
259             int jid;
260             if(tok.argv[1][0]=='%')
261                 jid=atoi((tok.argv[1])+sizeof(char));
262             else
263                 jid=pid2jid(atoi(tok.argv[1]));
264             job=getjobjid(job_list,jid);
265             if(tok.builtins==BUILTIN_BG) {
266                 printf("[%d] (%d) %s\n",job->jid,job->pid,job->cmdline);
267                 job->state=BG;
268                 kill(-(job->pid),SIGCONT);
269             } else {
270                 job->state=FG;
271                 kill(-(job->pid),SIGCONT);
272             }
273         }
274     }
275
276     dup2(stdi, STDIN_FILENO);
277     dup2(stdo, STDOUT_FILENO);
278     if(infg!=-1)
279         close(infg);
280     if(outfg!=-1)
281         close(outfg);
282     return;
283 }
284
285 /*
286  * parseline - Parse the command line and build the argv array.
287  *
288  * Parameters:
289  *   cmdline:  The command line, in the form:
290  *
291  *                command [arguments...] [< infile] [> oufile] [&]
292  *
293  *   tok:      Pointer to a cmdline_tokens structure. The elements of this
294  *             structure will be populated with the parsed tokens. Characters
295  *             enclosed in single or double quotes are treated as a single
296  *             argument.
297  * Returns:
298  *   1:        if the user has requested a BG job
299  *   0:        if the user has requested a FG job
300  *  -1:        if cmdline is incorrectly formatted
301  *
302  * Note:       The string elements of tok (e.g., argv[], infile, outfile)
303  *             are statically allocated inside parseline() and will be
304  *             overwritten the next time this function is invoked.
305  */
306 int parseline(const char *cmdline, struct cmdline_tokens *tok) {
307
308     static char array[MAXLINE]; /* holds local copy of command line */
309     const char delims[10] = " \t\r\n"; /* argument delimiters (white-space) */
310     char *buf = array; /* ptr that traverses command line */
311     char *next; /* ptr to the end of the current arg */
312     char *endbuf; /* ptr to the end of the cmdline string */
313     int is_bg; /* background job? */
314
315     int parsing_state; /* indicates if the next token is the
316      input or output file */
317
318     if (cmdline == NULL) {
319         (void) fprintf(stderr, "Error: command line is NULL\n");
320         return -1;
321     }
322
323     (void) strncpy(buf, cmdline, MAXLINE);
324     endbuf = buf + strlen(buf);
325
326     tok->infile = NULL;
327     tok->outfile = NULL;
328
329     /* Build the argv list */
330     parsing_state = ST_NORMAL;
331     tok->argc = 0;
332
333     while (buf < endbuf) {
334         /* Skip the white-spaces */
335         buf += strspn(buf, delims);
336         if (buf >= endbuf)
337             break;
338
339         /* Check for I/O redirection specifiers */
340         if (*buf == '<') {
341             if (tok->infile) {
342                 (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
343                 return -1;
344             }
345             parsing_state |= ST_INFILE;
346             buf++;
347             continue;
348         }
349         if (*buf == '>') {
350             if (tok->outfile) {
351                 (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
352                 return -1;
353             }
354             parsing_state |= ST_OUTFILE;
355             buf++;
356             continue;
357         }
358
359         if (*buf == '\'' || *buf == '\"') {
360             /* Detect quoted tokens */
361             buf++;
362             next = strchr(buf, *(buf - 1));
363         } else {
364             /* Find next delimiter */
365             next = buf + strcspn(buf, delims);
366         }
367
368         if (next == NULL) {
369             /* Returned by strchr(); this means that the closing
370              quote was not found. */
371             (void) fprintf(stderr, "Error: unmatched %c.\n", *(buf - 1));
372             return -1;
373         }
374
375         /* Terminate the token */
376         *next = '\0';
377
378         /* Record the token as either the next argument or the input/output file */
379         switch (parsing_state) {
380         case ST_NORMAL:
381             tok->argv[tok->argc++] = buf;
382             break;
383         case ST_INFILE:
384             tok->infile = buf;
385             break;
386         case ST_OUTFILE:
387             tok->outfile = buf;
388             break;
389         default:
390             (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");
391             return -1;
392         }
393         parsing_state = ST_NORMAL;
394
395         /* Check if argv is full */
396         if (tok->argc >= MAXARGS - 1)
397             break;
398
399         buf = next + 1;
400     }
401
402     if (parsing_state != ST_NORMAL) {
403         (void) fprintf(stderr,
404                 "Error: must provide file name for redirection\n");
405         return -1;
406     }
407
408     /* The argument list must end with a NULL pointer */
409     tok->argv[tok->argc] = NULL;
410
411     if (tok->argc == 0) /* ignore blank line */
412         return 1;
413
414     if (!strcmp(tok->argv[0], "quit")) { /* quit command */
415         tok->builtins = BUILTIN_QUIT;
416     } else if (!strcmp(tok->argv[0], "jobs")) { /* jobs command */
417         tok->builtins = BUILTIN_JOBS;
418     } else if (!strcmp(tok->argv[0], "bg")) { /* bg command */
419         tok->builtins = BUILTIN_BG;
420     } else if (!strcmp(tok->argv[0], "fg")) { /* fg command */
421         tok->builtins = BUILTIN_FG;
422     } else {
423         tok->builtins = BUILTIN_NONE;
424     }
425
426     /* Should the job run in the background? */
427     if ((is_bg = (*tok->argv[tok->argc - 1] == '&')) != 0)
428         tok->argv[--tok->argc] = NULL;
429
430     return is_bg;
431 }
432
433 /*****************
434  * Signal handlers
435  *****************/
436
437 /*
438  * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
439  *     a child job terminates (becomes a zombie), or stops because it
440  *     received a SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU signal. The
441  *     handler reaps all available zombie children, but doesn't wait
442  *     for any other currently running children to terminate.
443  */
444 void sigchld_handler(int sig) {
445     pid_t pid;
446     int status;
447     while ((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) {
448         if (WIFSTOPPED(status)) {
449             int jid=pid2jid(pid);
450             if(jid!=0) {
451                 printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));
452                 (getjobpid(job_list,pid))->state=ST;
453             }
454         } else if (WIFSIGNALED(status)) {
455             if (WTERMSIG(status) == SIGINT) {
456                 int jid=pid2jid(pid);
457                 if(jid!=0) {
458                     printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,SIGINT);
459                     deletejob(job_list,pid);
460                 }
461             }
462             else
463                 deletejob(job_list, pid);
464         } else
465             deletejob(job_list, pid);
466     }
467     return;
468 }
469
470 /*
471  * sigint_handler - The kernel sends a SIGINT to the shell whenver the
472  *    user types ctrl-c at the keyboard.  Catch it and send it along
473  *    to the foreground job.
474  */
475 void sigint_handler(int sig) {
476     pid_t pid = fgpid(job_list);
477     if (pid != 0) {
478         kill(-pid, sig);
479     }
480     return;
481 }
482
483 /*
484  * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
485  *     the user types ctrl-z at the keyboard. Catch it and suspend the
486  *     foreground job by sending it a SIGTSTP.
487  */
488 void sigtstp_handler(int sig) {
489     pid_t pid = fgpid(job_list);
490     if (pid != 0) {
491         kill(-pid, sig);
492     }
493     return;
494 }
495
496 /*********************
497  * End signal handlers
498  *********************/
499
500 /***********************************************
501  * Helper routines that manipulate the job list
502  **********************************************/
503
504 /* clearjob - Clear the entries in a job struct */
505 void clearjob(struct job_t *job) {
506     job->pid = 0;
507     job->jid = 0;
508     job->state = UNDEF;
509     job->cmdline[0] = '\0';
510 }
511
512 /* initjobs - Initialize the job list */
513 void initjobs(struct job_t *job_list) {
514     int i;
515
516     for (i = 0; i < MAXJOBS; i++)
517         clearjob(&job_list);
518 }
519
520 /* maxjid - Returns largest allocated job ID */
521 int maxjid(struct job_t *job_list) {
522     int i, max = 0;
523
524     for (i = 0; i < MAXJOBS; i++)
525         if (job_list.jid > max)
526             max = job_list.jid;
527     return max;
528 }
529
530 /* addjob - Add a job to the job list */
531 int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline) {
532     int i;
533
534     if (pid < 1)
535         return 0;
536
537     for (i = 0; i < MAXJOBS; i++) {
538         if (job_list.pid == 0) {
539             job_list.pid = pid;
540             job_list.state = state;
541             job_list.jid = nextjid++;
542             if (nextjid > MAXJOBS)
543                 nextjid = 1;
544             strcpy(job_list.cmdline, cmdline);
545             if (verbose) {
546                 printf("Added job [%d] %d %s\n", job_list.jid,
547                         job_list.pid, job_list.cmdline);
548             }
549             return 1;
550         }
551     }
552     printf("Tried to create too many jobs\n");
553     return 0;
554 }
555
556 /* deletejob - Delete a job whose PID=pid from the job list */
557 int deletejob(struct job_t *job_list, pid_t pid) {
558     int i;
559
560     if (pid < 1)
561         return 0;
562
563     for (i = 0; i < MAXJOBS; i++) {
564         if (job_list.pid == pid) {
565             clearjob(&job_list);
566             nextjid = maxjid(job_list) + 1;
567             return 1;
568         }
569     }
570     return 0;
571 }
572
573 /* fgpid - Return PID of current foreground job, 0 if no such job */
574 pid_t fgpid(struct job_t *job_list) {
575     int i;
576
577     for (i = 0; i < MAXJOBS; i++)
578         if (job_list.state == FG)
579             return job_list.pid;
580     return 0;
581 }
582
583 /* getjobpid  - Find a job (by PID) on the job list */
584 struct job_t *getjobpid(struct job_t *job_list, pid_t pid) {
585     int i;
586
587     if (pid < 1)
588         return NULL;
589     for (i = 0; i < MAXJOBS; i++)
590         if (job_list.pid == pid)
591             return &job_list;
592     return NULL;
593 }
594
595 /* getjobjid  - Find a job (by JID) on the job list */
596 struct job_t *getjobjid(struct job_t *job_list, int jid) {
597     int i;
598
599     if (jid < 1)
600         return NULL;
601     for (i = 0; i < MAXJOBS; i++)
602         if (job_list.jid == jid)
603             return &job_list;
604     return NULL;
605 }
606
607 /* pid2jid - Map process ID to job ID */
608 int pid2jid(pid_t pid) {
609     int i;
610
611     if (pid < 1)
612         return 0;
613     for (i = 0; i < MAXJOBS; i++)
614         if (job_list.pid == pid) {
615             return job_list.jid;
616         }
617     return 0;
618 }
619
620 /* listjobs - Print the job list */
621 void listjobs(struct job_t *job_list, int output_fd) {
622     int i;
623     char buf[MAXLINE];
624
625     for (i = 0; i < MAXJOBS; i++) {
626         memset(buf, '\0', MAXLINE);
627         if (job_list.pid != 0) {
628             sprintf(buf, "[%d] (%d) ", job_list.jid, job_list.pid);
629             if (write(output_fd, buf, strlen(buf)) < 0) {
630                 fprintf(stderr, "Error writing to output file\n");
631                 exit(1);
632             }
633             memset(buf, '\0', MAXLINE);
634             switch (job_list.state) {
635             case BG:
636                 sprintf(buf, "Running    ");
637                 break;
638             case FG:
639                 sprintf(buf, "Foreground ");
640                 break;
641             case ST:
642                 sprintf(buf, "Stopped    ");
643                 break;
644             default:
645                 sprintf(buf, "listjobs: Internal error: job[%d].state=%d ", i,
646                         job_list.state);
647             }
648             if (write(output_fd, buf, strlen(buf)) < 0) {
649                 fprintf(stderr, "Error writing to output file\n");
650                 exit(1);
651             }
652             memset(buf, '\0', MAXLINE);
653             sprintf(buf, "%s\n", job_list.cmdline);
654             if (write(output_fd, buf, strlen(buf)) < 0) {
655                 fprintf(stderr, "Error writing to output file\n");
656                 exit(1);
657             }
658         }
659     }
660     if (output_fd != STDOUT_FILENO)
661         close(output_fd);
662 }
663 /******************************
664  * end job list helper routines
665  ******************************/
666
667 /***********************
668  * Other helper routines
669  ***********************/
670
671 /*
672  * usage - print a help message
673  */
674 void usage(void) {
675     printf("Usage: shell [-hvp]\n");
676     printf("   -h   print this message\n");
677     printf("   -v   print additional diagnostic information\n");
678     printf("   -p   do not emit a command prompt\n");
679     exit(1);
680 }
681
682 /*
683  * unix_error - unix-style error routine
684  */
685 void unix_error(char *msg) {
686     fprintf(stdout, "%s: %s\n", msg, strerror(errno));
687     exit(1);
688 }
689
690 /*
691  * app_error - application-style error routine
692  */
693 void app_error(char *msg) {
694     fprintf(stdout, "%s\n", msg);
695     exit(1);
696 }
697
698 /*
699  * Signal - wrapper for the sigaction function
700  */
701 handler_t *Signal(int signum, handler_t *handler) {
702     struct sigaction action, old_action;
703
704     action.sa_handler = handler;
705     sigemptyset(&action.sa_mask); /* block sigs of type being handled */
706     action.sa_flags = SA_RESTART; /* restart syscalls if possible */
707
708     if (sigaction(signum, &action, &old_action) < 0)
709         unix_error("Signal error");
710     return (old_action.sa_handler);
711 }
712
713 /*
714  * sigquit_handler - The driver program can gracefully terminate the
715  *    child shell by sending it a SIGQUIT signal.
716  */
717 void sigquit_handler(int sig) {
718     printf("Terminating after receipt of SIGQUIT signal\n");
719     exit(1);
720 }
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-146882-1-1.html 上篇帖子: beaglebone_black_学习笔记——(4)闪烁LED之shell命令 下篇帖子: [批处理教程之Shell]001.文本处理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表