Monthly Archives: November 2019

Controlling Shell Commands through C

With the ever growing presence of embedded systems, it has become a very common requirement to do some tasks through shell commands while doing others through C in an embedded system. Very often people achieve this by using system() C library function, which is known for its inefficiencies and limitations. So, is there a better way to achieve it? Yes. The main C (commander) program may spawn a master shell script process, which would then keep on accepting & executing shell commands from the commander program. Here are the two components (a main C program and a master script) of the framework, put out:

/* File: commander.c */

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

int main(char argc, char *argv[])
{
	int pfds[2]; // 0 is read end, 1 is write end
	int wfd;
	pid_t pid;
	char cmd[100];
	int cmdlen;
	int stop, status;

	if (pipe(pfds) == -1)
	{
		perror(argv[0]);
		return 1;
	}

	pid = fork();
	if (pid == -1)
	{
		perror(argv[0]);
		return 2;
	}
	else if (pid != 0) // Parent
	{
		close(pfds[0]); // Close the read end of the pipe
		wfd = pfds[1];
		// Continue doing other stuff, e.g.
		// take commands from user and pass onto the master script
		stop = 0;
		do
		{
			printf("Cmd (type \"done\" to exit): ");
			if ((status = scanf("%[^\n]", cmd)) <= 0)
			{
				getchar(); // Remove the \n
				continue;
			}
			getchar(); // Remove the \n
			if (strcmp(cmd, "done") == 0)
			{
				stop = 1;
			}
			else
			{
				cmdlen = strlen(cmd);
				cmd[cmdlen++] = '\n';
				//cmd[cmdlen] = '\0';
				// Pass on the command to master script
				write(wfd, cmd, cmdlen);
			}
		}
		while (!stop);
		close(wfd);
	}
	else
	{
		close(pfds[1]); // Close the write end of the pipe
		dup2(pfds[0], 0); // Make stdin the read end of the pipe
		if (execl("./master_script.sh", "master_script.sh", (char *)(NULL))
				== -1)
		{
			perror("Master script process spawn failed");
		}
	}

	return 0;
}

# File: master_script.sh

#!/bin/bash

while read cmd
do
	#echo "Running ${cmd} ..."
	${cmd}
done

One may compile the commander.c and try it as follows:

$ gcc commander.c -o commander
$ ./commander

This approach becomes even more powerful, when the shell command execution is pretty often. Also note that the main thread need not block for the command execution to complete. Though, it can be customized to block as well, if required. And many more customizations can be achieved as desired, e.g. getting the command output back into the C program instead of stdout, redirecting the command error into some log file instead of stderr, getting the command status – to list a few. Post your comments below to discuss any customizations of your interest.

   Send article as PDF