Skip to content

Instantly share code, notes, and snippets.

@yorickdewid
Created June 6, 2015 22:14
Show Gist options
  • Select an option

  • Save yorickdewid/50b298542c7e1e85b1d2 to your computer and use it in GitHub Desktop.

Select an option

Save yorickdewid/50b298542c7e1e85b1d2 to your computer and use it in GitHub Desktop.
Shell template in C. Add your program commands to the shell and link it with your program.
/*
* ------------------------------- shell.c ---------------------------------
*
* Copyright (c) 2015, Yorick de Wid <yorick17 at outlook dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* This shell acts an an template to easily test a program by adding some
* commands to the right sections. These so called local commands are
* executed first, but when no local command is found the input is forked
* and executed as a terminal program. The shell implementation was written
* by Anders H at https://gist.github.com/parse/966049
*
* Compile as:
* cc -std=c99 -Wall shell.c -o shell
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
/* The array below will hold the arguments: args[0] is the command. */
static char* args[512];
pid_t pid;
int command_pipe[2];
static char line[1024];
static int n = 0; /* number of calls to 'command' */
#define READ 0
#define WRITE 1
#define ASZ(a) sizeof(a)/sizeof(a[0])
void local_exit() {
exit(0);
}
void local_test() {
puts("test called");
}
/* Add new functions here */
struct localfunction {
char name[64];
char description[128];
void (*vf);
} localfunclist[] = {
{"exit", "Exit the shell", &local_exit},
{"test", "Simple test function", &local_test},
/* Add new commands here */
};
/* Check if local command exist and execute it
*/
static int localcommand(int argc) {
int i;
void (*vfunc)(void);
for (i=0; i<ASZ(localfunclist); ++i) {
/* Show a list of available commands */
if (!strcmp("commands", args[0])) {
printf(" %s\t\t%s\n", localfunclist[i].name, localfunclist[i].description);
continue;
}
if (!strcmp(localfunclist[i].name, args[0])) {
vfunc = localfunclist[i].vf;
vfunc();
return 1;
}
}
return 0;
}
/*
* Handle commands separatly
* input: return value from previous command (useful for pipe file descriptor)
* first: 1 if first command in pipe-sequence (no input from previous pipe)
* last: 1 if last command in pipe-sequence (no input from previous pipe)
*
* EXAMPLE: If you type "ls | grep shell | wc" in your shell:
* fd1 = command(0, 1, 0), with args[0] = "ls"
* fd2 = command(fd1, 0, 0), with args[0] = "grep" and args[1] = "shell"
* fd3 = command(fd2, 0, 1), with args[0] = "wc"
*
* So if 'command' returns a file descriptor, the next 'command' has this
* descriptor as its 'input'.
*/
static int command(int input, int first, int last) {
int pipettes[2];
/* Invoke pipe */
pipe(pipettes);
pid = fork();
/*
SCHEME:
STDIN --> O --> O --> O --> STDOUT
*/
if (pid == 0) {
if (first == 1 && last == 0 && input == 0) {
// First command
dup2(pipettes[WRITE], STDOUT_FILENO);
} else if (first == 0 && last == 0 && input != 0) {
// Middle command
dup2(input, STDIN_FILENO);
dup2(pipettes[WRITE], STDOUT_FILENO);
} else {
// Last command
dup2(input, STDIN_FILENO);
}
if (execvp(args[0], args) == -1)
_exit(EXIT_FAILURE); // If child fails
}
if (input != 0)
close(input);
// Nothing more needs to be written
close(pipettes[WRITE]);
// If it's the last command, nothing more needs to be read
if (last == 1)
close(pipettes[READ]);
return pipettes[READ];
}
/* Final cleanup, 'wait' for processes to terminate.
* n : Number of times 'command' was invoked.
*/
static void cleanup(int n) {
int i;
for (i=0; i<n; ++i)
wait(NULL);
}
char *skipwhite(char *s) {
while (isspace(*s)) ++s;
return s;
}
int split(char* cmd) {
cmd = skipwhite(cmd);
char* next = strchr(cmd, ' ');
int i = 0;
while(next != NULL) {
next[0] = '\0';
args[i] = cmd;
++i;
cmd = skipwhite(next + 1);
next = strchr(cmd, ' ');
}
if (cmd[0] != '\0') {
args[i] = cmd;
next = strchr(cmd, '\n');
next[0] = '\0';
++i;
}
args[i] = NULL;
return i;
}
int run(char *cmd, int input, int first, int last) {
int argc = split(cmd);
if (args[0] != NULL) {
n += 1;
if (localcommand(argc))
return 0;
return command(input, first, last);
}
return 0;
}
int main(int argc, char *argv[]) {
printf("Template shell. Type 'commands' to show a list of options.\n");
while (1) {
/* Print the command prompt */
printf(">>> ");
fflush(NULL);
/* Read a command line */
if (!fgets(line, 1024, stdin))
return 0;
int input = 0;
int first = 1;
char *cmd = line;
char *next = strchr(cmd, '|'); /* Find first '|' */
while (next != NULL) {
/* 'next' points to '|' */
*next = '\0';
input = run(cmd, input, first, 0);
cmd = next + 1;
next = strchr(cmd, '|'); /* Find next '|' */
first = 0;
}
input = run(cmd, input, first, 1);
cleanup(n);
n = 0;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment