How to pass variable to shell command in C? - c

For example, I code :
fp = popen("wc -l < myfile", "r");
But myfile should be any file's name which is parsed to this project. It could be file abc.txt or 123.txt or xy.txt etc.
Then I want to get the output of executing this wc -l < myfile. But the problem is that I don't know which function in C can help me to parse the name of the myfile to this shell command and I can also get the output.
Can anyone gives me some suggestions?
Edit:
The file I want to read is very large. I want to read its data into an array.I cannot use list to store it, because it is too slow to locate a specific data in list. The problem is that if I use one dimensional array to malloc() memory space to the array, there is no enough continuous memory space on the laptop. Therefore, I plan to use two dimensional array to store it. So I have to get the num of lines in the file and then decide the size of each dimensional in this array via log.
Thanks for all answers. This project is about reading two files. The first file is much larger than the second file. The second file is like:
1 13 0
2 414 1
3 10 0
4 223 1
5 2 0
The third num in each line is called "ID". For example, num "1" has ID 0, num "2" has ID 1, num "3" has ID "0". (Ignore the middle num in each line)
And the first file is like:
1 1217907
1 1217908
1 1517737
1 2
2 3
2 4
3 5
3 6
If each num in the first file has the ID "0", I should store the both of num in each line into an data structure array. For example, we can see that num "1" has ID "0" in second file, so I need to store:
1 1217907
1 1217908
1 1517737
1 2
from my first file into the data structure array. The num "2" has ID"1" but num "3" has ID "0" and num "4" has ID "1", so need to store : 2 3 but not store 2 4 from my first file. That's why I need use array to store the two files. If I use two arrays to store them, I can check whether this num's ID is "0" fast in the array belongs to second file because using array is fast to locate a specific data, the index can be the value of the num directly.

I think, you need to make use of snprintf() to generate the string to be passed to popen() first and then you can call popen() with that string.
Pseudo-code
char buf[32] = {0};
snprintf(buf, 32, "wc -l < %s", myfile);
fp = popen(buf, "r");
EDIT
To make it work for any length of myfile
int len = strlen(myfile) + strlen("wc -l < ") + 1;
char *buf = malloc(len);
snprintf(buf, len, "wc -l < %s", myfile);
fp = popen(buf, "r");
...
free(buf);
Note: As mentioned by Ed Heal in the comment, the 32 here is used here for just demo purpose. You should choose your temporary array length based on the length of the string held by myfile, plus the mandatory characters, plus null terminator, obviously.

If you're not going to do this yourself (without a shell), which you should, at least pass the filename in such a way that the shell will only ever interpret it as data rather than code to avoid potential for security incidents.
setenv("filename", "myfile"); /* put filename in the environment */
fp = popen("wc -l <\"$filename\"", "r"); /* check it from your shell script */

Forget popen - do it yourself
i.e.
FILE *f = fopen(argv[1], "r");
int lines = 0;
int ch;
while ((ch = fgetc(f)) != EOF) {
if (c == '\n') lines++;
}
EDIT - As the poster wants to load the whole file into memory
Add the checking for errors
FILE *f = fopen(argv[1], "r");
struct stat size;
fstat(fileno(f), &size);
char buf = malloc(size.st_size)
fread(buf, size.st_size, 1, f);
fclose(f);

All of the code below is untested. If I find time to test, I'll remove this caveat.
You can create your own wrapper to popen() to allow you to form an arbitrary command.
FILE * my_popen (const char *mode, const char *fmt, ...) {
va_list ap;
int result = 511;
for (;;) {
char buf[result+1];
va_start(ap, fmt);
result = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (result < 0) return NULL;
if (result < sizeof(buf)) return popen(buf, mode);
}
/* NOT REACHED */
return NULL;
}
Then, you can call it like this:
const char *filename = get_filename_from_input();
FILE *fp = my_popen("r", "%s < %s", "wc -l", filename);
if (fp) {
/* ... */
pclose(fp); /* make sure to call pclose() when you are done */
}
Here, we assume that get_filename_from_input() transforms the filename input string into something safe for the shell to consume.
It is rather complex (and error prone) to reliably fix up a filename into something the shell will treat safely. It is more safe to open the file yourself. However, after doing so, you can feed the file to a command, and then read out the resulting output. The problem is, you cannot use popen() to accomplish this, as standard popen() only supports unidirectional communication.†
†Some variations of popen() exist that support bidirectional communication.
FILE * my_cmd_open (const char *cmd) {
int s[2], p, status, e;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) < 0) return NULL;
switch (p = fork()) {
case -1: e = errno; close(s[0]); close(s[1]); errno = e; return NULL;
case 0: close(s[0]); dup2(s[1], 0); dup2(s[1], 1); dup2(s[1], 2);
switch (fork()) {
case -1: exit(EXIT_FAILURE);
case 0: execl("/bin/sh", "-sh", "-c", cmd, (void *)NULL);
exit(EXIT_FAILURE);
default: exit(0);
}
default: for (;;) {
if (waitpid(p, &status, 0) < 0 && errno == EINTR) continue;
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) break;
close(s[0]); close(s[1]); errno = EPIPE;
return NULL;
}
}
close(s[1]);
return fdopen(s[0], "r+");
}
To efficiently read an entire file into memory, you can use mmap().
void * mmap_filename (const char *filename, size_t *sz) {
int fd = open(filename, O_RDONLY);
if (fd < 0) return NULL;
struct stat st;
if (fstat(fd, &st) < 0) {
close(fd);
return NULL;
}
*sz = st.st_size;
void *data = mmap(NULL, *sz, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
return data != MAP_FAILED ? data : NULL;
}
Then, you can call it like this:
size_t sz;
void *data = mmap_filename(filename, &sz);
if (data) {
/* ... */
munmap(data, sz);
}
The example code above maps the entire file at once. However, the mmap() API allows you to map portions of the file from a particular offset into the file.

Related

How to read from a file and parse it

I have a file .txt containing some values formatted like this:
0,30,25,10
Now, I open up the file and store it into an array
char imposta_tratt[300];
FILE *fp;
fp = fopen("/home/pi/Documents/imposta_trattamento.txt", "r");
if (fp == 0) return;
fread(imposta_tratt, sizeof(imposta_tratt), 1, fp);
fclose(fp);
Now I expect to have the array filled with my data. I have the values separated by a , so I go on and parse it:
const char delim[2] = ",";
int t=0;
char *token = strtok(imposta_tratt, delim);
while (token!=NULL){
strcpy(tratt[t],token);
token = strtok(NULL, delim);
tratt[t]=token;
t++;
}
Here, referring to what's in the file .txt, I expect to have tratt[0]=0; tratt[1]=30; tratt[2]=25; and so on, but seems like I am missing something since it's not like this.
All I want is to have the values of the txt file stored in single variables. Can someone help?
What you are trying to achieve can simply be done using fgets():
bool read_file_content(const char *filename, const size_t tsizemax, int tratt[tsizemax], size_t *tsize, const char *delim)
{
// Attempt to open filename.
FILE *fp = fopen(filename, "r");
if (!fp) return false; // Return false upon failure.
// Try to read one line. If you have more, you need a while loop.
char imposta_tratt[300];
if (!fgets(imposta_tratt, sizeof imposta_tratt, fp)) {
fclose(fp);
return false;
}
*tsize = 0;
char tmp[300]; // Temporary buffer. Used for conversion into int.
char *token = strtok(imposta_tratt, delim);
while (token && *tsize < tsizemax) {
strncpy(tmp, token, sizeof tmp);
tratt[(*tsize)++] = atoi(tmp);
token = strtok(NULL, delim);
}
fclose(fp);
return true;
}
const char *filename: The file you want to parse.
const size_t tsizemax: The maximum size of your tratt array. It is important to control the size, otherwise your code will have buffer overflow (think of when your file has more than 100 tokens, for example).
int tratt[tsizemax]: The array that will hold the values.
size_t *tsize: The number of tokens read (used in combination of tsizemax).
const char *delim: The delimiter(s), in your case a ,.
This is your main():
int main(void)
{
int tratt[100];
size_t size = 0;
if (!read_file_content("in.txt", 100, tratt, &size, ",")) {
puts("Failed");
return 1;
}
for (size_t i = 0; i < size; ++i)
printf("%d\n", tratt[i]);
}
Output:
0
30
25
10
Suppose "in.txt" has contents
0,30,25,10
The below program uses fscanf to read the integers into the tratt array, one-by-one. As we read integers using fscanf, we make sure it's return value is as expected. If not, we close the file and exit. In the event that the return value of fscanf is not as expected, the program also prints which type of error occurred. Currently, if any error occurs, the program stops. However, you can make the program behave differently depending on the error that occurred if you like.
As output, the program prints all of the integers read into the tratt array. The output is
0
30
25
10
Now this program assumes we know the number of elements we want to read into tratt. If we do not, we could allow for dynamically allocating more memory should the array need more elements or perhaps "in.txt" could contain a data structure, say, at the beginning/end of the file that records information about the file, such as the number of numbers in the file and the data type (a binary file would be best suited for this). These are just a couple of the possibilities.
A better approach might be to read characters in one-by-one (say, using getc) and use strtol to convert a sequence of character digits to a long int (I would have taken an approach similar to this).
Nevertheless, this approach is more succinct and should suffice.
#include <stdio.h>
#include <stdlib.h>
#define FILE_NAME "in.txt"
#define MAX_LEN 4
int main(void) {
int i, tratt[MAX_LEN];
FILE *fp = fopen(FILE_NAME, "r"); /* open file for reading */
/* if cannot open file */
if (fp == NULL) {
printf("Cannot open %s\n", FILE_NAME);
exit(EXIT_FAILURE);
}
/* read integer, checking return value of scanf as expected */
if (fscanf(fp, "%d", &tratt[0]) != 1) {
if (ferror(fp))
printf("fscanf: read error\n");
else if (feof(fp))
printf("fscanf: end of file\n");
else
printf("fscanf: matching failure\n");
fclose(fp);
exit(EXIT_FAILURE);
}
for (i = 1; i < MAX_LEN; i++)
/* read comma plus integer, checking return value of scanf */
if (fscanf(fp, ",%d", &tratt[i]) != 1) {
if (ferror(fp))
printf("fscanf: read error\n");
else if (feof(fp))
printf("fscanf: end of file\n");
else
printf("fscanf: matching failure\n");
fclose(fp);
exit(EXIT_FAILURE);
}
fclose(fp); /* close file */
/* print integers stored in tratt */
for (i = 0; i < MAX_LEN; i++)
printf("%d\n", tratt[i]);
return 0;
}

read write binary into a string c

So, I looked around the internet and a couple questions here and I couldn't find anything that could fix my problem here. I have an assignment for C programming, to write a program that allows user to enter words into a string, add more words, put all words in the string to a text file, delete all words in string, and when they exit it saves the words in a binary, which is loaded upon starting up the program again. I've gotten everything to work except where the binary is concerned.
I made two functions, one that loads the bin file when the program starts, one that saves the bin file when it ends. I don't know in which, or if in both, the problem starts. But basically I know it's not working right because I get garbage in my text file if I save it in a text file after the program loads the bin file into the string. I know for sure that the text file saver is working properly.
Thank you to anyone who takes the time to help me out, it's been an all-day process! lol
Here are the two snippets of my functions, everything else in my code seems to work so I don't want to blot up this post with the entire program, but if need be I'll put it up to solve this.
SIZE is a constant of 10000 to meet program specs of a 1000 words. But I couldn't get this to run even asking for only 10 elements or 1, just to clear that up
void loadBin(FILE *myBin, char *stringAll) {
myBin = fopen("myBin.bin", "rb");
if (myBin == NULL) {
saveBin(&myBin, stringAll);
}//if no bin file exists yet
fread(stringAll, sizeof(char), SIZE + 1, myBin);
fclose(myBin); }
/
void saveBin(FILE *myBin, char *stringAll) {
int stringLength = 0;
myBin = fopen("myBin.bin", "wb");
if (myBin == NULL) {
printf("Problem writing file!\n");
exit(-1);
stringLength = strlen(stringAll);
fwrite(&stringAll, sizeof(char), (stringLength + 1), myBin);
fclose(myBin); }
You are leaving bad values in your myBin FILE*, and passing the & (address) of a pointer.
Pass the filename, and you can (re) use the functions for other purposes, other files, et al.
char* filename = "myBin.bin";
Pass the filename, buffer pointer, and max size to read. You should consider using stat/fstat to discover file size
size_t loadBin(char *fn, char *stringAll, size_t size)
{
//since you never use myBin, keep this FILE* local
FILE* myBin=NULL;
if( NULL == (myBin = fopen(fn, "rb")) ) {
//create missing file
saveBin(fn, stringAll, 0);
}//if no bin file exists yet
size_t howmany = fread(stringAll, sizeof(char), size, myBin);
if( howmany < size ) printf("read fewer\n");
if(myBin) fclose(myBin);
return howmany;
}
Pass the file name, buffer pointer, and size to save
size_t saveBin(char *fn, char *stringAll, size_t size)
{
int stringLength = 0;
//again, why carry around FILE* pointer only used locally?
FILE* myBin=NULL;
if( NULL == (myBin = fopen("myBin.bin", "wb")) ) {
printf("Problem writing file!\n");
exit(-1);
}
//binary data may have embedded '\0' bytes, cannot use strlen,
//stringLength = strlen(stringAll);
size_t howmany = fwrite(stringAll, sizeof(char), size, myBin);
if( howmany < size ) printf("short write\n");
if(myBin) fclose(myBin);
return howmany;
}
Call these; you are not guaranteed to write & read the same sizes...
size_t buffer_size = SIZE;
char buffer[SIZE]; //fill this with interesting bytes
saveBin(filename, buffer, buffer_size);
size_t readcount = loadBin(filename, buffer, buffer_size);

How to use the read function, with an unknown size

I made a function that opens a file and reads the single characters of them.
int getBlocks(char *file){
char c;
int line = 0;
int pos = 0;
int blocks;
FILE *inptr = fopen(file, "rt");
// Count the amount of blocks/tetriminos
while((c=fgetc(inptr))!=EOF){
if(c == '.' || c == '#'){
pos++;
if(pos == 4){
pos = 0;
line++;
}
}
}
blocks = (line/4);
fclose(inptr);
return blocks;
}
Now I have to rewrite it, because the only functions I am allowed to use are exit, open, close, write, read, malloc and free.
I think I could basically use int inptr = open(file, O_RDONLY); instead of my fopen line, and simply close(inptr); instead of my fclose function.
My only problem now is fgetc. I am pretty sure that I can use read here, but according to it's definition ssize_t read(int fildes, void *buf, size_t nbytes); I would need to tell a fixed amount of bytes in advance, but the filesize can always differ in my case.
Do you know how I could rewrite fgetc here?
It's pretty similar with slight changes
char chr;
int fd = open(file, O_RDONLY);
if (fd == -1)
reutnr -1; // Or some error integer
while (read(fd, &chr, 1) == 1) {
/* the rest of your code */
}
close(fd);
Note that one important change is that the type of chr is char and not int.
Instead of checking for EOF you simply check that read() returned a suitable value, ideally you should store it somewhere and check that it's 0 at the end of the loop, meaning the end of the file was reached, otherwise an error occurred.

Reading a file from a specified line to another

I currently have a shell script in C that takes a command of a file, two numbers, mth line and nth line, and supposedly should output whats in between those lines:
for example:
./output file1 2 6
would output file from line 2 to line 6
I have it implemented currently in a way of outputting the whole file, I have been trying to change it to output specifically between those lines
this is my code
#include <fcntl.h>
int main(int argc, char *argv[])
{
int file;
int size;
int l[1000];
int firstNum = atoi(argv[2]);
int secondNum = atoi(argv[3]);
file = open(argv[1], O_RDONLY);
if( file == -1)
{
printf("\n cannot open file \n");
}
while ((size=read(file,l,80)) > 0)
write(1,l,size);
}
I tried to change l and size to firstNum and secondNum, which are the numbers entered from the command line, but still did not work and outputted one single line.
What is a better way of doing so ?
There are several issues with your code, so just go through them sequentially:
It's better to use high level fopen rather than low level open to open a file. So it's better to write this way:
FILE *file = fopen(argv[1], "r");
if (file == NULL)
exit(EXIT_FAILURE);
Your read is wrong as it reads exactly 80 characters instead of a line as you expect.
while ((size=read(file,l,80)) > 0) // <-- WRONG because this reads 80 chars instead of one line
For similar reason as with open, it's better to use alternative like printf instead of low level read and write.
To read line by line, you should use library function getline.
To control what line number to print, a simple way is to have a variable tracking what line number and compare with your command line arguments.
So put them together, you would need something like this:
FILE *file = fopen(argv[1], "r");
if (file == NULL)
exit(EXIT_FAILURE);
int line_num = 0;
char * line = NULL;
size_t len = 0;
ssize_t read = 0;
while ((read = getline(&line, &len, file)) != -1)
{
line_num++;
if( firstNum <= line_num && line_num <= secondNum )
{
printf( "line %d: %s", line_num, line );
if( line_num == secondNum )
break;
}
}
Try looking at this post: C read file line by line
From there it should be a simple matter of counting lines read and discarding them until you have read firstNum lines at which point print until you've reached secondNum.

Using popen("ls -la") produces strange result

I wrote some C code for it to get the result of an "ls -la" command using popen and write the result into an C. The code looks like this:
unsigned int ls(char *destination, const char *username, const char *relative_path)
{
printf("LS IMP\n");
//if(!username || !relative_path) return -1;
FILE *ls_pipe = NULL;
unsigned long ls_pipe_size = -1;
const char ls_command[] = "ls -la ";
char ls_path[255] = "/home/";
char ls_full_command[255];
char buffer[255];
bzero(buffer, 255);
char *entries = NULL;
bzero(ls_full_command, 255);
strcat(ls_path, username);
strcat(ls_path, relative_path);
strcat(ls_full_command, ls_command);
strcat(ls_full_command, ls_path);
printf("AFTER CATS\n");
ls_pipe = popen(ls_full_command, "r");
if(ls_pipe == NULL) return -1;
printf("Pipe ok!");
fseek(ls_pipe, 0, SEEK_END);
ls_pipe_size = ftell(ls_pipe);
rewind(ls_pipe);
printf("Filesize: %lu\n", ls_pipe_size);
int i;
for(i = 0; i < 100; i++)
{
fread(buffer, 1, 255, ls_pipe);
printf("%s", buffer);
}
//entries = (char*) malloc(sizeof(char) * ls_pipe_size);
//if(entries == NULL) return -1;
printf("Entries ok!\n");
//if(ls_pipe_size != fread(destination, sizeof(char), ls_pipe_size, ls_pipe)) return -1;
fclose(ls_pipe);
return strlen(destination);
}
The problem is the size of the pipe is huge (?) and in the result after the proper result three entries start to appear non-stop for like infinity.
Is there any way of reading from it without knowing the exact number of lines of the result using something like another popen with wc -l?
Thanks
P.S there are some modifications in the code when i was trying to test what's going wrong and the malloc didn't work because of the insane size of the pipe.
You can't seek on a pipe — period. Any value you get back from ftell() is immaterial or erroneous. You can't rewind a pipe because you can't seek on a pipe. You can only read data once from a pipe.
So, you need to redesign the code to read an indefinite amount of data.
Here's some reasonably working code — but I needed to adapt it to Mac OS X and my machine, so instead of /home/ it uses /Users/, and the call to ls() uses my user name. The code properly handles buffers full of data that do not end with a null (listing about 570 lines of output for my bin directory). I've left the interface to ls unchanged although it almost doesn't use destination and returning the length of destination is otherwise unrelated to what it is doing. It also uses pclose() to close the pipe. Using pclose() avoids leaving zombies around and returns the exit status of the executed program where fclose() will not.
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static unsigned int ls(char *destination, const char *username, const char *relative_path)
{
printf("LS IMP\n");
assert(destination != 0 && username != 0 && relative_path != 0);
const char ls_command[] = "ls -la ";
char ls_path[255] = "/Users/";
char ls_full_command[255];
snprintf(ls_full_command, sizeof(ls_full_command), "%s %s%s/%s",
ls_command, ls_path, username, relative_path);
FILE *ls_pipe = popen(ls_full_command, "r");
if (ls_pipe == NULL)
return -1;
printf("Pipe ok!\n");
char buffer[255];
int nbytes;
while ((nbytes = fread(buffer, 1, 255, ls_pipe)) > 0)
printf("%.*s", nbytes, buffer);
putchar('\n');
printf("Entries ok!\n");
pclose(ls_pipe);
return strlen(destination);
}
int main(void)
{
unsigned int length = ls("/", "jleffler", "bin");
printf("ls() returned %u\n", length);
return(0);
}

Resources