Implementing environment variables in simple linux shell - c

I am to program a simple shell in linux that can implement various stuffs including environment variables. I tried printing these variables using getenv but I have some problems. getenv always return NULL even if the user types a correct variable like $HOME for example. Here is my code
int i = 0;
if(strcmp(cmdArgv[i], "echo") == 0){
char *variable;
for(i = 1; cmdArgv[i] != NULL; i++){
variable = getenv(cmdArgv[i]);
if(!variable){
puts("not a variable");
printf("%s ", cmdArgv[i]);
}else{
puts("a variable");
printf("%s ", variable);
}
}
printf("\n");
exit(0);
}
It doesn't enter into the else condition. For example if the user types echo ls $HOME. This input is parsed into the cmdArgv which is a char **. Then the output I have is
not a variable
ls
not a variable
$HOME
BUT $HOME is a variable so maybe my implementation of getenv isn't right. Any ideas as to what seem to be the problem? Thanks.

The variable is called HOME, not $HOME. (The latter is your shell's syntax for expanding the variable.)

Related

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

Why is one seg faulting and the other not?

Hi I have a program that needs to compare a string array to a predefined string but when I use the variable args[0] it works in my function strcmp as below
int gash_execute(char **args){
int i;
if(args[0] == NULL){
return 1;
}
for(i = 0; i < gash_command_num(); i++){
if(strcmp(args[0], functions[i]) == 0){
return(*function_address[i])(args);
}
}
return gash_launch(args);
}
However when trying to strcmp args[i] such as below I get a seg fault. Can anyone help me find a solution to this problem?
int gash_execute(char **args){
int i;
if(args[0] == NULL){
return 1;
}
for(i = 0; i < gash_command_num(); i++){
if(strcmp(args[i], functions[i]) == 0){
return(*function_address[i])(args);
}
}
return gash_launch(args);
}
args[] is an array of strings that were previously separated by spaces, this program is for a custom shell, so pretend in my shell command line I input "cat echo ls" args[0] would be "cat" and so on. However now I need to implement i/o redirection. So i need to check every element of args to check if they represent the symbols "<" ">" "|" and if one of them is we can take it from there
Without seeing all the code, or a report from a tool such as valgrind, I can't say for sure. But I can tell you that this loop is full of potential problems.
for(i = 0; i < gash_command_num(); i++){
if(strcmp(args[i], functions[i]) == 0){
return(*function_address[i])(args);
}
}
It's iterating through three arrays (args, functions, and function_address) based on some function call which takes none of those as variables (gash_command_num()) which has an unknown relation to how many elements are actually in those arrays.
And it's using two global variables (functions and function_addresses) which could contain anything.
If all those things are related, I would suggest making that explicit... but I suspect they're not. I suspect your loop logic is wrong. It's comparing args[i] with functions[i]. I suspect gash_command_num() is actually the size of functions and so the loop is walking off args.
What I suspect you really want to do is to see if args[0] matches any function name in functions and then call the related function. If args[0] is ls then you want to check if there's a built in shell function for ls and call it with all the arguments.
Rather than searching a list over and over again, and having to manage two parallel lists, this would be much better served with a hash table. The keys are the function names, the values are the function pointers. C doesn't have a hash table built in, but there's plenty of libraries for that. Gnome Lib is a solid choice for this and many other basic functionality that C is missing.
Using a hash table, and eliminating the globals, your code reduces to this:
/* For encapsulation */
typedef int(*shell_function_t)(char **);
int gash_execute(char **args, GHashTable *shell_functions){
int i;
if(args[0] == NULL){
return 1;
}
shell_function_t func = g_hash_table_lookup(shell_functions, args[0]);
if( func ) {
return(*func)(args);
}
else {
return gash_launch(args);
}
}
Scanning for pipes is now a separate problem. For that you do want to loop through args looking for special characters. That is made much simpler if, when you create args, you ensure it ends with a null pointer.
for(int i = 0; args[i] != NULL; i++) {
...check if args[i] is special...
}

Using ftw() properly in c

I have the following in my code: (Coding in c)
ftw(argv[2], parseFile, 100)
argv[2] is a local directory path. For instance. argv[2] = "TestCases" and there is a testcases folder in the same directory as my .o file.
My understanding is that this should traverse the directory TestCases and send every file it finds to the function parseFile.
What actually happens is it simply sends my argument to the function parseFile and that is all. What am I doing wrong? How am I suppose to use this properly?
EDIT: This is parseFile:
int parseFile(const char * ftw_filePath,const struct stat * ptr, int flags){
FILE * file;
TokenizerT * currFile;
char fileString[1000], * currWord, * fileName;
fileName = strdup(ftw_filePath);
if( fileName == NULL || strlen(fileName) <= 0){
free(fileName);
return -1;
}
printf("\n%s\n",fileName);
if(strcmp(fileName,"-h")== 0){
printf("To run this program(wordstats) type './wordstat.c' followed by a space followed by the file's directory location. (e.g. Desktop/CS211/Assignment1/test.txt )");
free(fileName);
return 1;
}
else{
file=fopen(fileName,"r");
}
if(!file){
fprintf(stderr,"Error: File Does not Exist in designated location. Please restart the program and try again.\n");
free(fileName);
return 0;
}
memset(fileString, '\0', 1000);
while(fscanf(file,"%s", fileString) != EOF){ /* traverses the file line by line*/
stringToLower(fileString);
currFile = TKCreate("alphanum",fileString);
while((currWord = TKGetNextToken(currFile)) != NULL) {
insert_List(currWord, words,fileName);
}
free(currFile->delimiters);
free(currFile->copied_string);
free(currFile);
memset(fileString, '\0', 1000);
}
fclose(file);
free(fileName);
return 1;
}
It will work if I input TestCases/big.txt for my argv[2] but not if I put TestCases
As described in the man page, a non-zero return value from the function that ftw is calling tells ftw to stop running.
Your code has various return statements, but the only one that returns 0 is an error condition.
A properly designed C callback interface has a void* argument that you can use to pass arbitrary data from the surrounding code into the callback. [n]ftw does not have such an argument, so you're kinda up a creek.
If your compiler supports thread-local variables (the __thread storage specifier) you can use them instead of globals; this will work but is not really that much tidier than globals.
If your C library has the fts family of functions, use those instead. They are available on most modern Unixes (including Linux, OSX, and recent *BSD)

Environment variables in simple linux shell

I am to program a simple shell in C for my project that can implement environment variables. I looked up on how to use the getenv, setenv, putenv. So far so good i've tried to use the getenv to show the shell variables...well... with some succes . But I have a feeling that my reasoning is flawed. I have a char** argv which contains parsed input from the user input. I now check if argv starts with the command "echo" and then if any of the following inputs starts with a $ sign or not. Here's my code:
int executeVariables(char** arguments){
int i = 0;
if(strcmp(arguments[i], "echo") == 0){
char *variable;
for(i = 1; arguments[i] != NULL; i++){
char *str = arguments[i];
if( *(str + 0) == '$'){
variable = getenv(str + 1);
}else{
variable = getenv(str);
}
if(!variable){
//puts("not a variable");
printf("%s ", arguments[i]);
}else{
//puts("a variable");
printf("%s ", variable);
}
}
printf("\n");
exit(0);
}
return 1;
}
I think that normal linux shell finds the $ sign, it expands the variable before invoking the echo command. My shell isn't following this principle, it's expanding variables inside the echo command itself. Any idea as to how I can implement this? Thanks.
EDIT:
A problem I have is: echo $HOME and echo HOME gives me the same result which is wrong.
EDIT:
After various tests everything works well. But to really test it i'll need to create a local variable then echo this value. I tried it using putenv function but it doesn't create the local variable.
i = 0;
char** temp = malloc(sizeof (*temp));
if(strstr(userInput, "=") != NULL){
//puts("we got equals");
puts(userInput);
if(putenv(userInput) == 0){
printf("doing putenv(%s)\n", userInput);
exit(0);
}
else{
puts("couldnt putenv");
exit(1);
}
}
userInput: char *userInput is the input gotten from the command line using fgets()
You're specifically asking the code to do getenv() for the string, even if $ isn't found. That's why it will lookup $HOME or HOME. Just remove the else case for not finding the dollar-sign, and make sure to initialize variable to NULL at its declaration, and put it inside the loop.
Something like so:
// First, perform replacements
int executeVariables(char** arguments){
int i = 0;
for(i = 0; arguments[i] != NULL; i++){
char *str = arguments[i];
if(*str == '$'){
// make sure the result isn't NULL, though I'm not sure a real shell does
char *tmp = getenv(str + 1);
if (tmp != NULL) {
arguments[i] = getenv(str + 1); // save off the argument
}
}
}
// Then actually execute the function. This would be like bash's echo builtin
if (strcmp(arguments[0], "echo") == 0) {
int i;
for (i = 1; arguments[i] != NULL; i++) {
printf("%s ", arguments[i]);
}
printf("\n");
}
// Other functions could go here
return 1;
}
Edit: As far as your methodology goes, why do you specifically check for echo? Why not make it generic, and check all the arguments, including the first one? You actually probably want to substitute all the potential environment variables, so if you had MYECHO=echo somewhere, the following would work. To make it more generic, you'd have this function, which would then execute the stuff based on the expanded variables. You could make it all nice and have separate functions, but to fit it all in here, I've updated the code above accordingly, though I haven't tested it. ;)
Edit: That being said, werewindle's comment about doing this earlier does apply -- replace the arguments, like here, and then use that updated arguments array to have a separate function do whatever it needs to do. :)
> $MYVAR totally awesome $HOME
totally awesome /home/user
Edit: As for the putenv() situation, you'll want something structured like the following. This way, it will set it for the shell, and any other processes you run in the shell.
void do_args_env(char *args[])
{
// do putenv, etc.
}
// inside main loop in shell process
while (1) { // just an example
if (check_args_syntax(args) != 0) {
// error
}
do_args_env(args);
// fork and do other stuff
}
(Hopefully final) Edit: As an explanation, processes generally don't (perhaps can't?) affect the environment of processes above them in their hierarchy; only the other way around. So if you putenv() in a child, that child's siblings (i.e. other processes forked from its parent) won't get the environment change.
Glad I could be of help!
Yes, normal shells expand variables during parsing command line. So you need substitute variables in function, that produces array that "contains parsed input from the user input". In this case code for echo (and other commands) will be much shorter.

How to get the modified environmental variables using c programme in Mac using bash terminal

I want to get the modified environment variables using C a program on a Mac using a bash terminal. How do I do this?
If I use getenv, I will get only the default system defined environment variables - but I am not getting the modified one. Why? And how would I correct this?
#include <stdio.h>
int main()
{
setenv("PATH","/mypath",1);
printf("%s\n",getenv("PATH"));
return(0);
}
The above program outputs:
/mypath
If however you execute env in bash after your C program, you will get the PATH value which is set by default for bash.
$ env
...
PATH=/usr/lib/qt-3.3/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/user/bin
...
This is because the environment variables are modified only for that particular process which is running the C program, & not for the process running bash.
Edit:
Writing env.c as:
#include <stdio.h>
int main()
{
printf("%s\n",getenv("PATH"));
return(0);
}
followed by:
$ gcc env.c
$ export PATH=/bin
$ ./a.out
gives:
/bin
I don't see why it should be any different. (Did you do all the steps that I have done above?)
By using putenv(), setenv() and unsetenv() we only affect the environment variables of current process and child process that we invoke. we can not affect the environment of parent process, which is often a shell.
By using extern char** environ; we can not access the modified environment variables,But the
program which is used to add or modified the environment variables can access these using getenv() function.
For example....
#include<stdio.h>
#include<stdlib.h>
main()
{
if(putenv("COLOR=BROWN")==0)
printf("Successful\n");
else
printf("Unsuccessful\n");
//If we put an environment variable it will only VISIBLE THE PROGRAMME BY WHICH IT HAS BEEN set
char *result;
result = getenv("COLOR");//char* getenv(const char*)
if(result!=NULL)//if environment variable does not exist result will be NULL
puts(result);
else
printf("failed to get environment variable\n");
//Modify this environment variable
if(putenv("COLOR=RED")==0)
printf("changing Successful\n");
else
printf("Unsuccessful\n");
result = getenv("COLOR");
if(result!=NULL)
puts(result);
else
printf("failed to get environment variable\n");
//Verify with existing env variables
if(putenv("PWD=NONE")==0)
printf("changing Successful\n");
else
printf("Unsuccessful\n");
result = getenv("PWD");
if(result!=NULL)
puts(result);
else
printf("failed to get environment variable\n");
}
Try this:
#include <stdio.h>
main(int *a, char *b[], char *c[])
{
int ctr;
for (ctr = 0; c[ctr]; ctr++)
{
printf("%s\n", c[ctr]);
}
return 0;
}

Resources