Unix tail program in C using an implemented function - c

I have implemented my own dynamic-memory version of getline function:
char * fgetline(FILE * f)
Starts with 30 character buffer and when the buffer is full allocate a new one copy the contents and free the old buffer. When we get EOF or \n we return from the function.
I want to use this function to implement a version of the program tail. Input comes from stdin, output goes to stdout. If the first argument begins with -, everything after the - is the number of lines to print. The default number of lines to print is 10, when no argument is given.
I have thought until now that I should use the function:
int atoi (const char *s)
from stdlib.h and have an array of pointers to lines but I don't know exactly how to do this.
Any ideas?

Declare your main function as
int main (int argc, char**argv) {
}
If you compile your program to myprog executable, and invoke it as myprog -20 somefile anotherfile then you have:
argc == 4
&& strcmp(argv[0], "myprog") == 0
&& strcmp(argv[1], "-20") == 0
&& strcmp(argv[2], "somefile") == 0
&& strcmp(argv[3], "anotherfile") == 0
&& argv[4] == NULL
in other words, you might want to have your program containing
int nblines = 10;
int main(int argc, char**argv) {
int argix = 1;
if (argc>1) {
if (argv[1][0]=='-')
{
nblines = atoi(argv[1]+1);
argix = 2;
}
for (; argix < argc; argix++)
tail (argv[argix]);
}
return 0;
}
It is up to you to implement your void tail(char*filename); function appropriately. Don't forget to compile with all warnings & debugging info, e.g. with gcc -Wall -g on Linux. Use your debugger (gdb on Linux) to debug your program. Take into account that fopen can fail, and use errno to display an appropriate error message.
Notice that you don't need your fgetline function. The getline(3)
function is standard (in Posix 2008) and is dynamically allocating the line buffer.

Related

Backspace and multibyte characters using getchar() in C

I was reading BeeJ's C programming guide and copied his readline() function, that reads a line from stdin. Due to the way it's implemented it has no problem reading multibyte characters, as it reallocates the spaces in accordance with the total amount of received bytes, and as such, it has no problem with unicode input. Here's a program with the function included:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define printPrompt printf("db > ")
/* The readLine function, allocates memory for a short string and
** reads characters into it. When the string's size limit is met,
** the same memory block is reallocated, but twice the size.
** Shamelessly stolen from BeeJ's guide to C programming |=
*/
char* read_line(void) {
int i = 0; /* Position of the current character */
int linbuf = 4; /* Size of our line in memory, will be
duplicated once the line length surpasses it */
char* lin; /* The pointer value to our line */
int c; /* The value we'll use to accept characters */
if( !(lin = malloc( linbuf*sizeof(char))) )
return NULL;
while( c = getchar(), c != '\n' && c != EOF ) {
/* Check if the amount of bytes accepted has surpassed the
* amount of memory we've allocated so far */
if(i == linbuf - 1) {
/* If it did, reallocate double the space */
linbuf *= 2;
char* tmpbuf = realloc(lin, linbuf);
/* If the space couldn't have been allocated then we'd
* run out of memory. Delete everything and abort. */
if(tmpbuf == NULL) {
free(tmpbuf);
return NULL;
}
/* If we've arrived here that means there were no
* problems, so we'll assign the newly reallocated
* memory to "lin" */
lin = tmpbuf;
}
/* Add the new character to our allocated space */
lin[i++] = c;
}
/* If we've received an EOF signal after having read 0
* characters, we'd like to delete our allocated memory and
* return a NULL */
if(c == EOF && i == 0) {
free(lin);
return NULL;
}
/* Here we'll shrink the allocated memory to perfectly fit our
* string */
if(i < linbuf - 1) {
char* tmpbuf = realloc(lin, i + 1);
if(tmpbuf != NULL)
lin = tmpbuf;
}
/* Here we'll terminate the string */
lin[i] = '\0';
/* Finally, we'll return it */
return lin;
}
int main(int argc, char* argv[]) {
char* hey = read_line();
printf("%s\n", hey);
return 0;
}
An input of
Hello, World! (:
would result in the output of
Hello, World! (:
An input of multibyte characters such as
שלום, עולם! (:
would result in the correct output of
שלום, עולם! (:
However, if I were to press the backspace key, it would only delete a one byte character, resulting in garbled output; an input of (backspaces marked as \b):
שיהיה לכם בוקר טוב\b\b\b\b\b\b\b\bערב טוב
which is supposed to end up being:
שיהיה לכם ערב טוב
actually ends up being:
�שיהיה לכם בוק�ערב טוב
My computer runs a Musl-libc version of Void Linux, and I compiled the program with both tcc and gcc, both yielding the same results.
Does this problem have to do with my libc, with my terminal (suckless st), with my kernel, or is it something I'm missing in the code? Whatever might be the case, is there any way I can handle it, preferably without using any external libraries such as ICU or what have you?
"is there any way I can handle it [...] without using any external libraries" The answer is a big fat no. Unless you are prepared to write a big and complex library yourself, that is.
With external libraries this is trivial:
sudo apt install libreadline-dev # no idea how to say that in Void
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h> // optional, to enable line history
int main()
{
using_history(); // optional
char* s;
while ((s = readline("Type something > ")))
{
printf("You have typed: %s\n", s);
add_history(s);
}
printf ("Bye!\n");
}
With this, you get a bunch of goodies for free, including full Unicode-aware line editing, programmable keybindings, and input history.
Edit On a machine I first checked this, your program behaved like you describe. But on another machine, which happens to be my home desktop, it works as expected, no weird backspace behaviour at all. I checked both X11 terminals and the text linux tty. So I guess there is something with some kernels and/or terminals after all.
Later edit There is an stty setting that controls this behaviour, at least for UTF-8.
stty iutf8
and your program should behave as expected, with no big fat libraries needed.

strtol resulting in a segmentation fault on the raspberry pi 3 b+

I have this problem that strtol doesn't work and results in my C program crashing. I am using a Raspberry Pi 3 b+ but that probably doesn't matter.
My program (which is a command line tool to control shift registers) uses strtol for parsing through the command line arguments the program gets.
Here's the output I get: fish: Job 2, “./a.out -p 16 -w 0xff” terminated by signal SIGSEGV (Addroundary error)
And here's the output on gdp:
Program received signal SIGSEGV, Segmentation fault.
__GI_____strtol_l_internal (
nptr=0x76f72968 <_nl_C_LC_CTYPE_toupper+512> "",
nptr#entry=0x7efff6e9 "16", endptr=0x7efff6e9,
base=<optimized out>, group=group#entry=0,
loc=0x76fa1c70 <_nl_global_locale>) at strtol_l.c:484
484 strtol_l.c: No such file or directory.
The following is the code:
else if(!strcmp(argv[arg], "-p") || !strcmp(argv[arg], "-pins")) {
if(argc <= arg+1)
return 1;
pins = strtol(argv[++arg], endptr, 0);
}
The command line argument parsing happens like so:
uint8_t arg, pins;
char **endptr;
uint64_t writeValue;
bool valueGiven; // The other bools that I define are irrelevant
for(arg = 1; arg < argc; arg++) {
if(!strcmp(argv[arg], "-w") || !strcmp(argv[arg], "-write")) {
if(argc <= arg+1)
return 1;
writeValue = strtol(argv[++arg], endptr, 0); // error happens here too
valueGiven = true;
}
else if(!strcmp(argv[arg], "-p") || !strcmp(argv[arg], "-pins")) {
if(argc <= arg+1)
return 1;
pins = strtol(argv[++arg], endptr, 0);
}
// There are more arguments but those are irrelevant
}
And I run the program like this: ./a.out -p 16 -w 0xFF
This error is very odd for the exact same thing worked before, could this be a case of data corruption?
Your char **endptr is uninitialized. It needs to point to a char * where the address of the first unconverted character will be stored. Instead, yours points nowhere, so strtol is going to try to write to whatever bogus memory location it points to, and very likely crash.
GCC and clang should both issue warnings about the uninitialized variable if you enable -Wall. Example. Always use compiler warnings, and don't ignore them!
Normally you would declare a char * variable and pass its address:
char *end;
strtol(argv[++arg], &end, 0);

Create Multiple Files in C program using command line arguments

I am trying to create a C program which has a text.txt file with 50 lines. This text.txt file should be split into 5 files such as text_part1.txt, text_part2.txt and so on. The 50 lines in the text.txt file should be copied equally to 10 lines each in 5 files.
All these has to be done by using command line arguments. I am a beginner in C and have just started to code. I don't know how to use command line arguments.
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE *ptr_readfile;
FILE *ptr_writefile;
char line [100];
char fileoutputname[10];
int filecounter=1, linecounter=1;
ptr_readfile = fopen("C:/home/dir/sample_pg/data/text.txt","r");
if (!ptr_readfile)
return 1;
sprintf(fileoutputname, "file_part%d", filecounter);
ptr_writefile = fopen(fileoutputname, "w");
while (fgets(line, sizeof line, ptr_readfile)!=NULL)
{
if (linecounter == 5)
{
fclose(ptr_writefile);
linecounter = 1;
filecounter++;
sprintf(fileoutputname, "file_part%d", filecounter);
ptr_writefile = fopen(fileoutputname, "w");
if (!ptr_writefile)
return 1;
}
fprintf(ptr_writefile,"%s\n", line);
linecounter++;
}
fclose(ptr_readfile);
return 0;
}
To get the program's arguments, you need to define your main function with an argument count (conventionally named argc) and and argument array (conventionally named argv), so something as
int main(int argc, char**argv) {
for (int ix=1; ix<argc; ix++) {
FILE* fil = fopen(argv[ix], "r");
if (!fil) { perror(argv[ix]); exit(EXIT_FAILURE); };
When you compile this (with some other needed code) into an executable foo.exe and run foo.exe a b c on the terminal, argc is 4 and you have
argc == 4 &&
strcmp(argv[0], "foo.exe") == 0 &&
strcmp(argv[1], "a") == 0 &&
strcmp(argv[2], "b") == 0 &&
strcmp(argv[3], "c") == 0 &&
argv[4] == NULL
Notice that it is a good habit to call perror on failure of a function like fopen
BTW, you forgot to call fclose in your program. You might learn more about fflush also. And you should prefer snprintf to sprintf to avoid buffer overflows. Learn more about, and be very scared of, undefined behavior.
Please take the habit of compiling with all warnings & debug info (e.g. gcc -Wall -Wextra -g if using GCC....) then learn how to use the debugger.
Read perror(3), fopen(3), fclose(3), fflush(3), snprintf(3) and take the habit to read the documentation of every function that you want to use.
See also csplit; you might take some inspiration by studying the source code of the free software package coreutils implementing it on Linux.

Compare string literal command line parameters in C

I need my program to run this way ./src c 2345 or ./src s 345, whereby the first character hs to be either c or s and second an integer. The program should throw an usage error if there's any less parameters and also any charcter other than c or s. Here's my code
int main(int argc, char **argv) {
int num_of_connections = 0, client_sockfd;
int max_sockfd, master_socket;
fd_set socket_collection, read_collection;
// Check validity of the user input
if(argc < 3) {
if((strcmp(argv[2], "s") != 0) || (strcmp(argv[2], "c") != 0)){
fprintf(stderr, "Usage: ./src <s/c> <port>\n");
exit(EXIT_FAILURE);
}
}
When I enter one argument I get a segmentation fault. Also it doesnt identify the C or S parameter. Any help will be appreciated.
Notice that main has a very specific convention: the argv array has argc+1 members, with the last being NULL and the others being non-null distinct pointers to zero-terminated strings.
So if argc is 1 (e.g. if your run ./src alone) or 2, argv[2] is NULL and you cannot pass it to  strcmp
You can call strcmp(argv[2],"s") only when argc>=3
BTW, I would suggest to use getopt(3) or preferably (on Linux only) getopt_long and to accept the --help and --version arguments, per GNU conventions.
Also, compile with all warnings and debug info (gcc -Wall -g) and use the gdb debugger. It would be faster for you to use gdb than to ask here and wait for replies.
if(argc < 3) { does not make sense if you want exactly two parameters. In the inner if block you are confusion || (logical or) with && (logical and).
In your invocation example ./src s 345 the character is the first argument, so probably argv[2] should read argv[1].
if ((argc != 3) || ((strcmp(argv[1], "s") != 0) &&
(strcmp(argv[1], "c") != 0))) {
fprintf(…);
return EXIT_FAILURE;
}
Note: all parentheses in this if (…) condition are optional, because of C's operator precedence. I put them for readability.

Parse command line arguments in Lex

Suppose I want my Lex and Yacc program to parse the command line arguments like:
./a.out show memory
I want lex to parse the string "show memory". How do I accomplish this?
You'll need to concatenate all the arguments into a big string, by inserting whitespace between them. Then feed the remaining text buffer to Lex/Yacc, by re-defining the YY_INPUT macro so it reads input from your text buffer.
The start could be something like:
#include <stdio.h>
#include <string.h>
char *argbuf;
size_t arglen;
int main(int argc, char *argv[])
{
int i;
// Compute total length of all arguments, with a single space between.
arglen = 0;
for(i = 1; argv[i] != NULL; i++)
arglen += 1 + strlen(argv[i]);
// Allocate buffer space.
argbuf = malloc(arglen);
if(argbuf == NULL)
{
fprintf(stderr, "No memory for argument buffer, aborting");
exit(1);
}
// Concatenate all arguments. This is inefficient, but simple.
argbuf[0] = 0;
for(i = 1; argv[i] != NULL; i++)
{
if(i > 1)
strcat(argbuf, " ");
strcat(argbuf, argv);
}
// Here we should be ready to call yyparse(), if we had implemented YY_INPUT().
return 0;
}
int main(int argc, char **argv) {
if(argc > 1) {
if(argv[1])
yy_scan_string(argv[1]);
}
yyparse();
return 0;
}
What's wrong with doing it the old fashioned way?:
if(argc > 1 && !strcmp(argv[1],"show"))
{
if(argc > 2)
{
if(!strcmp(argv[2],"memory"))
...
else if(!strcmp(argv[2],"cpu"))
...
else ...
}
}
Besides, getopt() and friends are more appropriate.
My blog article Parsing command line parameters with Yacc & Flex explains this with a working example. There is no need to concatenate the argument string. The reason is given in the article.
The blurb is:
Every once in a while someone comes along and asks how to parse
command line parameters with Yacc & Flex. This is rather straight
forward, but requires some knowledge of the generated code to get
right.
Here we present a source template that does this. The user only has to
edit the grammar and scanning rules. Some knowledge of C, Yacc and
Flex is assumed.
The code is WTFPL licensed
The template is written for Berkeley Yacc and the reflex variant of
Flex. It may be made to work with GNU Bison and SourceForge Flex,
possibly with a few changes.
What you get is a template where you can just insert your lexical and grammar specification.
Please ask questions about the using and adapting the template itself to the blog comments.

Resources