using readline() to edit commands, in c - c

i have to implement a few CLI features and now I am trying to use readline() so that the user can edit or go through their commands. Its working so far in that it allows the user to enter their commands and scroll through the history. Its when the user tries to edit the command. The cursor somehow manages to pass the command and go into the prompt eg
"desktop r1234|5$: ls" where "desktop r12345(sp)$: " is the prompt showing working directory & root dir; and "|" is the cursor. The cursor should stop in-between '$' and 'ls' ie "desktop r12345$:| ls" .The showWrkngDir() method just displays a prompt just like a normal terminal.
int main (int argc, char * argv[])
{
showWrkngDir();
static char *line_read = (char *)NULL;
using_history();
rl_readline_name = basename(argv[0]);
if (line_read)
{
free (line_read);
line_read = (char *)NULL;
}
while(strcmp((line_read = readline ("")) , "EXIT") != 0)
{
if (line_read && *line_read)
add_history (line_read);
tokenize(line_read);
showWrkngDir();
}
return 0;
}
void showWrkngDir()
{
char curDir[MAX_COMMAND_SZ];
char *env;
getcwd(curDir, sizeof(curDir));
env = (char *)getenv("USER");
printf("%s ",basename(curDir));
printf("%s(sp)$ ", env);
}

I'm fairly sure that readline wants to display the prompt itself, because sometimes it needs to erase the entire screen line and redraw it from scratch. This happens especially when browsing through history, but also when editing a command that spills over to the next line, or when ^L is pressed.
Give your prompt as the argument to readline(),

Pass the prompt to readline and let it print it rather than printing it yourself using printf.

Related

How do you generally scan/parse commands in interactive REPL programs in C, is there a standard way?

I am writing an interactive REPL program in c.
Some examples of commands (lines starting with >) I would like to handle are:
$ ./my_program // run the program
> add user
id: 123 // this is the output of above command
> update user 123 name "somename"
> remove user 123
> quit
So basically the command is a line with multiple strings.
This is how I am trying to handle the commands.
scan the whole line
parse the command and get a corresponding int value unique to command
do whatever needs to be done for the command
#include <stdio.h>
int parse_cmd(const char *buffer)
{
// parse command
}
int main(int argc, const char **argv)
{
// init code
char buffer[100];
int cmd;
while (1) {
printf("> ");
scanf("%[^\n]%*c", buffer);
cmd = parse_cmd(buffer);
if (cmd < 0) {
printf("error: invalid command\n");
continue;
}
switch (cmd) {
// handle commands
}
}
// deinit code
}
There are a lot of cli programs I have seen that take command inputs in similar way.
I wonder if there is a general way of writing cli programs?
I can write code to parse the commands, just wanted to know the standard approach for such situations?
While there's no real standard way, quite a lot of opensource console tools with an interactive mode use the GNU readline library (https://tiswww.case.edu/php/chet/readline/rltop.html).
It's actually quite easy to use, even simpler than implementing everything 100% correctly by yourself.
Your example rebased on readline:
int main(int argc, const char **argv)
{
// init code
int cmd;
char* line;
while (1) {
line = readline("> ");
if (line) {
cmd = parse_cmd(line);
switch (cmd) {
// handle commands
default:
printf("error: invalid command\n");
}
free(line);
} else {
break;
}
}
// deinit code
}
This isn't any more complex than your example, but you immediately gain:
command line editing at the interactive prompt, with correct handling of each and every possible terminal
correct handling of EOF (important if stdin is redirected)
unlimited input line size
And it's not very hard to add a command history, with arrow-up and down to repeat previous lines, incremental search, optionally persisted to a file, et et.
There's not really a standard way to do it. This is not a 100% fair comparison, but your question is kind of like if there is a standard way to construct a compiler, because you are in fact constructing a language, although a very simple one.
But one reasonably common way that works fairly well for simple programs is this approach. Let's assume that we have two commands add and del. Create a function for both these commands. First we search for one of the strings "add " or "del ". Notice the spaces. Put a pointer on the next character and call the corresponding function with the rest of the line as argument and allow them to determine things.
Here is some pseudo:
parse(bufferptr)
word = getFirstWord(bufferptr)
ptr = bufferptr + strlen(word)
if word == "add"
return add(ptr)
else if word == "del"
return del(ptr)
return -1
add(bufferptr)
word = getFirstWord(bufferptr)
if userExist(word)
return -1
else
return addUser(word)
del(bufferptr)
word = getFirstWord(bufferptr)
if not userExist(word)
return -1
else
return delUser(word)
buffer = input()
int res = parse(buffer)

Can't write to stdin with popen opened with write

I'm trying to create a GUI for the linux version of windscribe, so I wish to communicate with the windscribe CLI from my C program. I chose to use popen.
I can write single commands with popen("command", "w"), but I want to add informations when asked, like login in the CLI :
$ windscribe login
Windscribe Username: myname
Windscribe Password: mypassword
So I keep the FILE opened and use fputs to keep sending informations.
I finally wrote this function:
void write_in_shell_commands(char** commands, int length){
FILE *fp = NULL;
// sending first command
if (length > 0) {
debug("Writing \"%s\" to shell.", commands[0]);
fp = popen(commands[0], "w");
}
// sending the rest
if (fp) {
debug("File successfully opened.");
for (int i = 1; i < length; i++){
debug("%s", "Writing to shell.");
fputs(commands[i], fp);
}
pclose(fp);
} else {
debug("Operation failed.");
}
}
But when I use this function (e.g.) for login, it doesn't behave as I expected, it seems the additional commands are lost and not taken into account.
char* cmd1 = "windscribe login";
char* cmd2 = "myname";
char* cmd3 = "mypassword";
char** commands = malloc(3 * sizeof(char*));
commands[0] = cmd1;
commands[1] = cmd2;
commands[2] = cmd3;
write_in_shell_commands(commands, 3);
I expect the program to :
Write cmd1 in shell
Write cmd2 in stdin
Write cmd3 in stdin
But actually this happens
DEBUG output/shellwriter.c:22: Writing "windscribe login" to shell.
DEBUG output/shellwriter.c:27: File successfully opened.
DEBUG output/shellwriter.c:30: Writing to shell.
DEBUG output/shellwriter.c:30: Writing to shell.
Windscribe Password: Windscribe Username:
It's actually asking for the password in the terminal and eventually fails because credentials are bad.
What's actually happening here ? Where are my second and third commands ? Am I using the wrong functions ?
cmd1, cmd2, cmd3 are pointers to char. So you should allocate memory for 3 pointers to char for commands which is a pointer to pointer to char.
char** commands = malloc(3*sizeof(char*));

basic CLI program in C

Okay so overall im trying to complete a basic CLI C program which will complete functions such as clear, quit, cd, ls, help (bring up the unix man) etc.. i altered my code and so far i have this, im getting segmination error when trying to execute the cd command part of the program, (im very new to c btw);
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main (int argc, char *argv[])
{
char input[] = " ";
char *argument;
while(strcmp(input, "quit")!= 0)
{
printf("$");
scanf ("%s", input);
if(strcmp(input,"clear") == 0)
{
printf("\e[1;1H\e[2J");
}
else if(strcmp(argv[1],"cd") == 0)
{
if(chdir(argv[2]) == -1)
{
printf("\n directory does not exists");
}
}
else if(strcmp(input, "echo") == 0)
{
char str[50];
scanf("%[^\n]+", str);
printf(" %s", str);
}
}
}
input is declared as a ' ' (space) character. It will never match 'cd'.
This is probably more along the lines of what you want to achieve, where the first parameter is the command (cd), and the second will be the directory:
int main (int argc, char *argv[])
{
char *argument;
if(strcmp(argv[1],"cd") == 0)
{
if(chdir(argv[2]) == -1)
{
printf("\n directory does not exists");
}
}
Edit Also please note that there is no need for the else satement. If chdir does not return an error, it will change the directory, thus no need to call it again in an else.
Additionally, another tip for using system calls in general, it would be of great help if you print the error number returned by the system upon a failure in system call. This will make things easier when things start going wrong. To do this simply include <errno.h>' and modify the printf to printerrno` which gives specific details about the error:
printf("Chdir error: %d", errno);
For instance chdir() does not only return an error when the directory does not exist, but also for example if you do not have permissions to view the contents of the directory. See the man page for a list of possible errors.
To implement your own shell, you need to take input directly from stdin, not from command-line arguments (argv) from another shell. The basic pattern is like this:
Read input
Execute command
Print results
Loop back to step 1

Executing a separated command using execvp in C

I've separated a given command from the user into substrings , here's the code :
int i;
char *line = malloc(BUFFER);
char *origLine = line;
fgets(line, 128, stdin); // get a line from stdin
// get complete diagnostics on the given string
lineData info = runDiagnostics(line);
char command[20];
sscanf(line, "%20s ", command);
line = strchr(line, ' ');
printf("The Command is: %s\n", command);
int currentCount = 0; // number of elements in the line
int *argumentsCount = &currentCount; // pointer to that
// get the elements separated
char** arguments = separateLineGetElements(line,argumentsCount);
// here we call a method that would execute the commands
if (execvp(*arguments,*argumentsCount) < 0) // execute the command
{
printf("ERROR: exec failed\n");
exit(1);
}
When I execute the command in execvp(*arguments,*argumentsCount) , it fails .
What's wrong ?
Thanks .
EDIT :
The input from the user is : ls > a.out , hence I have 3 strings , which are :
ls , > , a.out , and it fails .
Shell redirection won't work if you aren't invoking a shell. You also won't have path searching to find the ls program. Some options
use system() instead, and exit when it returns
exec a shell and have it run your command
setup redirection as a shell would, then fork and execute each required child program.
Also your command doesn't make a lot of sense, you probably want ¦ instead of > and may need to specify the directory of a.out if it is not in your path. Consider giving it a meaningful name as well.
From man page of execvp command:
int execvp(const char *file, char *const argv[]);
The second argument is a list of null-terminated C-strings as arguments to the command to be executed by execvp. But in your code, you pass an int as the second argument which is wrong.
If you have list of arguments in the variable arguments then call execvp as:
execvp(arguments[0],arguments);
When you run ls > a.out at the command-line, > and a.out are not arguments passed to the application; they're interpreted by the shell to redirect stdout.
So in short, it is not possible to do what you want to do.1
1. Well, it is, but not this way. Your application would need to interpret the arguments, create the file, and set up a stream redirect.

Optional read from STDIN in C

My application is basically a shell which expects an input of type cmd [x], where cmd is constant and x is optional. So cmd 1 is legal as well as cmd by itself - then I assume a default parameter for x.
I am doing this:
char cmd[64];
scanf("%s", cmd);
int arg;
scanf("%d", &arg); // but this should be optional
How can I read the integer parameter, and set it to a default if none is currently available in the prompt? I do not want the prompt to wait for additional input if it was not given in the original command.
I tried several versions using fgetc() and getchar() and comparing them to EOF but to no avail. Each version I tried ends up waiting on that optional integer parameter.
The easy way:
char b[73]; //powers of 2 magic is evil.
if(fgets(b,sizeof b,stdin) != NULL) {
char cmd[59]; //59, no command will ever be > 58. Ever I say.
int arg;
if(sscanf(b,"%58s %d",cmd,&arg) == 2) {
//handle cmd and arg
} else if(sscanf(b,"%58s",cmd) == 1) {
//handle cmd only
} else {
// :-/
}
}
Simple answer, you can't. The C runtime takes input from the OS, but doesn't control it. To do something like this you will need to interact directly with the OS using platform specific APIs.
Are you reading line-by-line? Can't you just read the whole command until you reach a "\n" (newline)? If you get two tokens before the newline, it is a command and the argument; if you read only one, it is the command only and you set the second argument to the default.
Here's a program that works (sorry my previous answer was made in haste).
int main(){
char cmd[100], line[100];
int man = 0;
printf("OK: ");
fgets(line, 100, stdin);
int num = sscanf(line,"%s %d",cmd,&man);
if (num==1)printf("one! %s %d\n", cmd, man);
else if (num==2)printf("two! %s %d\n", cmd, man);
}
fgets reads the line (with bounds checking), and sscanf will assess whether one or two tokens were entered.

Resources