C, pass AWK syntax as argument to execl - c

I want to run the following command from a C program to read the system's CPU and memory use:
ps aux|awk 'NR > 0 { cpu +=$3; ram+=$4 }; END {print cpu,ram}'
I am trying to pass it to the execl command and after that read its output:
execl("/bin/ps", "/bin/ps", "aux|awk", "'NR > 0 { cpu +=$3; ram+=$4 }; END {print cpu,ram}'",(char *) 0);
But in the terminal I am getting the following error:
ERROR: Unsupported option (BSD syntax)
I would like to know how to properly pass awk as argument to execl?

You can't do this here this way.
The problem is that you want to execute several commands. execl is for executing a single command. The statement you have is using shell syntax (notably the | )
You will have better luck combining it all up in a single string and using the system(3) call.

Instead of running awk and parsing awk's output, you can do the filtering and summation in C, which often can quickly become much more convenient. (It's about the same for the exact command you have here.)
#include <errno.h>
#include <stdio.h>
void ps_cpumem(FILE* f, double* cpu_total, double* mem_total) {
for (;;) {
char buf[2000];
if (!fgets(buf, sizeof buf, f)) {
return;
}
double cpu, mem;
if (sscanf(buf, "%*s %*s %lf %lf", &cpu, &mem) == 2) {
*cpu_total += cpu;
*mem_total += mem;
}
}
}
int main() {
errno = 0;
FILE* ps = popen("ps aux", "r");
if (!ps) {
if (errno == 0) puts("popen: memory allocation failed");
else perror("popen");
return 1;
}
double cpu = 0, mem = 0;
ps_cpumem(ps, &cpu, &mem);
int rc = pclose(ps);
if (rc == -1) return 1;
printf("%%cpu: %5.1f\n" "%%mem: %5.1f\n", cpu, mem);
return 0;
}
However, you can run the full command through popen, as it executes a shell:
FILE* output = popen("ps aux | awk 'NR > 0 { cpu +=$3; ram+=$4 }; END {print cpu,ram}'", "r");
// read from output to do with as you like

As Will suggested, popen() is what you want for capturing output for subsequent use inside your program. However, if you truly are wanting to do an exec operation, you can use the shell to do your bidding via execl():
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
printf("%s: execl returned unexpectedly: %d", argv[0],
execl("/bin/sh", "/bin/sh", "-c",
"ps aux | awk 'NR >0 { cpu += $3; ram+=$4}; END {print cpu, ram}'",
NULL));
exit(1);
}

Related

Redirect output of command line to a variable in C

Is there a way to redirect output of a command line which returns integer as an output to a variable in C?
for example, if the command is "cmd", then is there a way to redirect its output (an integer) and store it in variable in C?
I tried using popen and fgets but it seems to be working only with characters. Any suggestions?
It works perfectly fine with popen and fgets:
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
const char *cmd = argc > 1 ? argv[1] : "echo 42";
char buf[32];
FILE *fp = popen(cmd, "r");
if( fp == NULL ){
perror("popen");
return 1;
}
if( fgets(buf, sizeof buf, fp) == buf ){
int v = strtol(buf, NULL, 10);
printf("read: %d\n", v);
}
return 0;
}
If you want to convert a character string from the standard input, you could use fgets and then use atoi to convert the input to an integer.
If you want to convert the output of a command, let's say ls and store the output of the command to a variable, you could learn about fork, dup2, pipe, and exec function family.
More about this topic on this tutorial : Capture the output of a child in C. This tutorial also provide an example with popen if you want to keep things "high level".
Here is an even simpler example using popen() and fscanf():
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
FILE *fp = popen("date '+%s'", "r");
long seconds;
if (fp == NULL) {
fprintf(stderr, "popen failed: %s\n", strerror(errno));
return 1;
}
if (fscanf(fp, "%ld", &seconds) == 1) {
printf("epoch seconds: %ld\n", seconds);
pclose(fp);
return 0;
} else {
fprintf(stderr, "invalid program output\n");
pclose(fp);
return 1;
}
}

How to combine two files into a third using only linux system calls?

I am a complete noob when it comes to linux system calls interacting with c-code.
So far I have been able to open a single file, but that's about it. I'm unsure of how I would take a second file and combine both of those into a third.
For example, I have file1 with simple text contents, and file2 with the same, how could I combine both contents into file3 using only linux system calls? I know I have to use lseek to change the pointer, but unsure of how to utilize that.
here is what I have so far... I apologize for the scarcity:
This takes file1 and copies it to file2, I believe
#include <fcntl.h>
#include <unistd.h>
int copyfile(const char *file1, const char *file2)
{
int infile, outfile;
ssize_t nread;
char buffer[BUFSIZE]
if( (infile = open(file1, O_RDONLY)) == -1 )
return (-1);
if( (infile = open(file2, O_WRONLY|O_CREATE|O_TRUNC, PERM)) == -1 )
{
close (infile);
return (-2);
}
/*read from file1 BUFSIZE chars at a time*/
while ( nread = read (infile, buffer, BUFSIZE) )
{
// write buffer to output file
if (write (outfile, buffer, nread) < nread)
{
close(infile);
close(outfile);
return (-3);
}
}
close (infile)
close (outfile)
if (nread == -1)
return (-4);
else
return(0);
}
The files will be entered within the terminal as such:
lastnameCat.c file1 file2 file3
such that file1 and file2 are added together, and sent into file3.
You can use the copy_file_range system call for this. It is faster than using read and write calls as the copying is done inside the kernel. From the man page:
The copy_file_range() system call performs an in-kernel copy between two file descriptors without the additional cost of transferring data from the kernel to user space and then back into the kernel.
Here is an example of using it:
#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <unistd.h>
int do_copy(int infd, int outfd)
{
ssize_t bytes = 0;
do
{
bytes = copy_file_range(infd, NULL, outfd, NULL, SSIZE_MAX, 0);
}
while(SSIZE_MAX == bytes);
return bytes;
}
int concatenate(const char *inpath1, const char *inpath2, const char *outpath)
{
int infd1 = -1;
int infd2 = -1;
int outfd = -1;
int res = -1;
infd1 = open(inpath1, O_RDONLY);
if(infd1 < 0)
goto close;
infd2 = open(inpath2, O_RDONLY);
if(infd2 < 0)
goto close;
outfd = open(outpath, O_WRONLY|O_CREAT|O_TRUNC);
if(outfd < 0)
goto close;
res = do_copy(infd1, outfd);
if(res < 0)
goto close;
res = do_copy(infd2, outfd);
close:
if(infd1 >= 0)
close(infd1);
if(infd2 >= 0)
close(infd2);
if(outfd >= 0)
close(outfd);
return res;
}
The loop in do_copy allows for very large files which may exceed the maximum copy possible in a single call.
Based on your comments, it sounds like this doesn't have to be a C program as long as it is user friendly. As long as you can guarantee that it will be run in linux, just create a shell script and name it whatever you want. You could even give the shell script the same name as your c program executable and users wouldn't be able to tell the difference:
#!/bin/bash
cat $1 $2 > $3
Say that you name this script lastnameCat and make it executable with chmod +x ./lastnameCat. From then on you could simply do:
$ ./lastnameCat file1 file2 file3
You could also name this script lastnameCat.c if you wanted, but that is a bit deceptive in my opinion since it is not a C file, it is a bash script.

Get grep value using execl

I'm trying to make a program which invokes the ls and grep system calls using exec. Specifically I have to execute ls > tmp; grep ­-c pattern < tmp in order to count the number of files that fulfill the pattern. As you can see I save the content of ls in tmp file and then I want to use grep to count the files.
Let's supose pattern = txt. I'm trying things like the following code:
char *a = "ls > tmp";
char *b = " -c ";
char *fin = " < tmp";
char *comanda;
if((comanda = malloc(strlen(pattern)+strlen(pattern)+1)) != NULL){
comanda[0] = '\0'; // ensures the memory is an empty string
strcat(comanda,b);
strcat(comanda, pattern);
strcat(comanda,fin);
} else {
return -1;
}
ret = execl("/bin/sh","sh","-c",a,NULL);
ret = execl("/bin/sh","sh","-c",comanda, NULL);
But it shows me the following error: ls: cannot access > tmp: No such file or directory. So I don't know how to get the value of grep, because the execl function does not return the value, so how can I achieve the grep value?
To get the output of a command, you need to use a pipe.
Have a look at : Connecting n commands with pipes in a shell?
You could just do:
ls | grep -c pattern
If you just want to get files with a specific pattern in filename you might want to use find
find your_path/ -name "*pattern*" | wc -l
Have a look at Grabbing output from exec to get the output of execl
Here is an example, replace the 4th argument of execl with whatever you want :)
(execl("/bin/sh", "sh", "-c", "ls > tmp; grep -c 'pattern' < tmp", (char *)NULL);)
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
pipe(fd);
if (fork() == 0)
{
close(fd[0]);
dup2(fd[1], 1);
dup2(fd[1], 2);
close(fd[1]);
execl("/bin/sh", "sh", "-c", "find your_path -name '*pattern*' | wc -l", (char *)NULL);
}
else
{
char buffer[1024] = {0};
close(fd[1]);
while (read(fd[0], buffer, sizeof(buffer)) != 0)
{
write(1, buffer, strlen(buffer));
memset (buffer, 0, sizeof(buffer));
}
}
return 0;
}
You're not allocating the correct amount of space for comanda, because you don't add the sizes of all the variables correctly. So if the size is too small, you'll write outside the array bounds when you do all the strcat, and this will cause undefined behavior.
You don't need the temporary file, you can just pipe from ls to grep. I've also added quotes around the pattern in case it contains special characters.
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
char *a = "ls | grep -c '";
char *fin = "'";
char *pattern = "foo";
char *comanda;
if((comanda = malloc(strlen(a) + strlen(pattern) + strlen(fin) +1)) != NULL){
strcpy(comanda,a);
strcat(comanda,pattern);
strcat(comanda,fin);
} else {
return -1;
}
int ret = execl("/bin/sh","sh","-c", comanda, (char*)NULL);
perror("execl"); // Can only get here if there's an error
}

popen: intercepting user's input

I have a code which runs bc thru popen(). I can intercept the calculator's output and prepend it with "Output=" text. But how can I intercept what user's is writing to bc?
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
if(!(in = popen("bc", "r"))){
exit(1);
}
while(fgets(buff, sizeof(buff), in)!=NULL){
printf("Output = %s", buff);
}
pclose(in);
return 0;
}
You can combine bc and echo with a pipe: echo '12*4' | bc
Example typing 12*4:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
char cmd[512];
while (fgets(buff, sizeof(buff), stdin)!=NULL){
strcpy(cmd, "echo '");
strcat(cmd, buff);
strcat(cmd, "' | bc");
if(!(in = popen(cmd, "r"))){
exit(1);
}
fgets(buff, sizeof(buff), in);
printf("output:%s", buff);
}
pclose(in);
return 0;
}
Output:
david#debian:~$ ./demo
12*4
output:48
You need to use pipe() and fork/exec(). However, manual piping is quite complex:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int write_pipe[2], read_pipe[2];
pipe(read_pipe); pipe(write_pipe);
#define PARENT_READ read_pipe[0]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ write_pipe[0]
#define PARENT_WRITE write_pipe[1]
int child = fork();
if (child == 0) { /* in child */
close(PARENT_WRITE);
close(PARENT_READ);
dup2(CHILD_READ, 0); close(CHILD_READ);
dup2(CHILD_WRITE, 1); close(CHILD_WRITE);
execl("/usr/bin/bc", "/usr/bin/bc");
} else { /* in parent */
close(CHILD_READ);
close(CHILD_WRITE);
write(PARENT_WRITE, "2+3\n", 4);
char buff[512];
int output_len=read(PARENT_READ, buff, sizeof(buff));
write(1, buff, output_len);
close(PARENT_READ);
}
return 0;
}
What you're looking to do is to start a subprocess, then simultaneously:
When activity occurs on standard input, execute some function on that input before passing it to the subprocess.
When activity occurs on the subprocess output, execute some function on that output before passing it to standard output.
The system call that allows you to wait for activity on two handles is called poll, but before we do that, we need to create the handles and start the subprocess:
int a[2], b[2];
if(pipe(a)==-1)abort(); // for communicating with subprocess input
if(pipe(b)==-1)abort(); // for communicating with subprocess output
switch(fork()) {
case -1: abort();
case 0: dup2(a[0],0), dup2(b[1],1), execlp("/usr/bin/bc", "bc", 0); exit(1);
};
Note how pipe works: Data written to fildes[1] appears on (i.e., can be read from) fildes[0]. This means we want to read from the standard output of our subprocess, b[0] and write to the standard input of our subprocess a[1].
Before we do that, we can use the poll instruction to wait for activity on either standard input (fd #0), or the subprocess output (b[0]):
for(;;) {
struct pollfd p[2]={0};
p[0].fd = 0; p[1].fd = b[0];
p[0].events = p[1].events = POLLIN;
while (poll(p,2,-1) <= 0);
At this point, there is activity on at least one of these file descriptors. You can see which one by examining the .revents member.
if(p[0].revents & POLLIN) {
r = read(0, buffer, sizeof(buffer));
write(a[1], buffer, r); // check for errors, or perhaps modify buffer
}
if(p[1].revents & POLLIN) {
r = read(b[0], buffer, sizeof(buffer));
write(1, buffer, r); // check for errors, or perhaps modify buffer
}
Note especially we use the opposite member a[1] and b[0] from the member we dup2'd onto the subprocesses standard input (0) and standard output (1).
At this point you can loop back up to poll again:
}
Disconnects (like EOF, program crash, etc) will be presented as read() returning 0, so watch carefully for this case, and break; out of the loop if so desired.

Implementing Pipes in a C shell (Unix)

Basically I have created a shell using standard POSIX commands, I want to be able to Implement Piping as well. Right now it handles commands correctly, and can do background processing with &. But I need to be able to pipe using | and >> as well.
For example something like this:
cat file1 file2 >> file3
cat file1 file2 | more
more file1 | grep stuff
Here is the code I have currently. I also want to AVOID "SYSTEM" calls. I know U need to use dup2, but the way I did my code is a bit odd, so im hoping if someone can tell me if it is feasible to implement pipes in this code? thanks! I know dup2 is used, but also im def. confused at how to implement >> as WELL as |
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
void Execute(char* command[],bool BG)
{
//Int Status is Used Purely for the waitpid, fork() is set up like normal.
int status;
pid_t pid = fork();
switch(pid)
{
case 0:
execvp(command[0], command);
if(execvp(command[0], command) == -1)
{
cout << "Command Not Found" << endl;
exit(0);
}
default:
if(BG == 0)
{
waitpid(pid, &status, 0);
//Debug cout << "DEBUG:Child Finished" << endl;
}
}
}
bool ParseArg(char* prompt, char* command[], char Readin[],bool BG)
{
fprintf(stderr, "myshell>");
cin.getline(Readin,50);
prompt = strtok(Readin, " ");
int i = 0;
while(prompt != NULL)
{
command[i] = prompt;
if(strcmp(command[i], "&") == 0){
//Debug cout << "& found";
command[i] = NULL;
return true;
}
//Debug cout << command[i] << " ";
i++;
prompt = strtok(NULL, " ");
}
return false;
}
void Clean(char* command[])
{
//Clean Array
for(int a=0; a < 50; a++)
{
command[a] = NULL;
}
}
int main()
{
char* prompt;
char* command[50];
char Readin[50];
bool BG = false;
while(command[0] != NULL)
{
Clean(command);
BG = ParseArg(prompt, command, Readin, BG);
if(strcmp(command[0], "exit") == 0 || strcmp(command[0], "quit") == 0 )
{
break;
}
else
{
Execute(command,BG);
}
}
return 1;
}
Pipes and redirections are different, actually. To implement a redirection (such as >>) you have to use dup2 indeed. First, open the desired file with appropriate flags (for >> they'll be O_WRONLY|O_CREAT|O_APPEND). Second, using dup2, make stdout (file descriptor 1) a copy of this newly opened fd. Finally, close newly opened fd.
To create a pipe, you'll need a pipe syscall. Read its manpage, it contains example code. Then you'll also need dup2 to make file descriptors returned by pipe be stdin for one process and stdout for another, respectively.
You should be able to implement pipes and output redirection with your shell, but there are a few things I noticed:
Your code for reading input, parsing, and output are mixed together, you may want to separate this functionality.
strtok won't work very well as a parser for shell commands. It will work for very simple commands, but you may want to look into creating or finding a better parser. A command like echo "hello world" will be problematic with your current parsing method.
You may want to create a simple structure for holding your parsed commands.
Here is some pseudocode to get you started:
#define MAX_LINE 10000
#define MAX_COMMANDS 100
#define MAX_ARGS 100
// Struct to contain parsed input
struct command
{
// Change these with IO redirection
FILE *input; // Should default to STDIN
FILE *output; // Should default to STDOUT
int num_commands;
int num_args[MAX_COMMANDS]; // Number of args for each command
char* command_list[MAX_COMMANDS]; // Contains the programs to be run
char* args_list[MAX_COMMANDS][MAX_ARGS]; // The args for each command
boolean background_task;
boolean append;
}
int main()
{
char input[MAX_LINE];
while (1)
{
struct command cmd;
print_prompt();
read_input(input);
parse_input(input, &cmd);
execute(&cmd);
}
}
Good luck with this project!

Resources