I am trying to display, set & modify PATH environment variable from a C program. I am doing something like this:-
char *cmd[] = { "echo", "$PATH", (char *)0 };
if (execlp("echo", *cmd) == -1)
But I am not getting the results.
You should use getenv(), there's no need to go through a shell:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("PATH='%s'\n", getenv("PATH"));
return EXIT_SUCCESS;
}
But you won't be able to change the value. Environment variables are inherited into child processes, but the child has its own copy. You can't change the shell's environment from a different program, regardless in which language it's written. You can of course change your own process' value, but that's not what you asked to do.
In the shell itself, you can change its current environment settings, but only there. This is why you need to use "source" to run shells scripts that change the environment.
If you want to display $PATH, try this:
#include <stdlib.h>
printf("PATH: %s\n",getenv("PATH"));
if you want to modify it, use setenv() or putenv().
try this:
char *cmd[] = { "$PATH", (char *)0 };
if (execlp("echo", cmd) == -1)
#include <stdio.h>
#include <stdlib.h>
...
char *pPath;
pPath = getenv("PATH");
if (pPath!=NULL)
printf ("The current path is: %s",pPath);
putenv("PATH=somepath");
...
Better solutions already given, but by way of explanation; the $PATH variable is parsed and translated by the command shell, not the echo command itself. The solutions already suggested use getenv() to obtain the environment variable's value instead.
To invoke the command shell to perform this:
system( "echo $PATH" ) ;
but that solution is somewhat heavyweight since it invokes a new process and the entire command processor just to do that.
Related
How can you run execvp with a "*.c". I can get it to work with a full name but not a wildcard. Any help would be greatly appreciated. This is what I have so far.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
printf("running\n");
char* args[] = { "find", "-name", "one.c", NULL};
char * envp[] ={NULL};
int pid = fork();
switch(pid){
case -1:
perror("fork() failed");
exit(1);
case 0: // child
execvp(args[0], args);
printf("after the exec\n");
default: // parent
//wait(NULL);
if(wait(NULL) == -1){
perror("wait() failed");
}
}
return 0;
}
You must do your own wildcard expansion. When you use the exec() family of functions, you are passing arguments almost directly to the new program.
If you want the replacing program to replace wildcards for you, you may well want to use a shell to do that (as system() does), but be careful, as you'll need to correctly quote for the shell.
Example:
char shell[] = "/bin/sh\0-c\0ls *.c";
char *args[] = { shell, shell+8, shell + 11, 0 };
execv("ls", args);
Note also that string literals are const char*, so shouldn't be used to populate a char*[].
However, in the case of find, you probably don't want to expand the wildcard. Here, there's no need to do anything special - just pass *.c as one of the arguments. The find command (specifically the -name argument) requires a pattern, not a list of filenames, so there's no expansion to do:
char shell[] = "/usr/bin/find\0.\0-name\0*.c";
char *args[] = { shell, shell+14, shell + 16, shell+22, 0 };
execv("find", args);
This is by design. Wildcard processing can be expensive because it requires to browse a folder. It is normally active by default in the shell, but not in API functions. One notable exception is system because it actually pass a command to the shell:
The system() function hands the argument string to the command inter-
preter sh(1)
The exec... family functions do not do that and assume that the path is a real path and do not special processing for wildcard characters. Simply, the exec[lv]p take all folder from the PATH environment variable and try to find a file with the exact name in one of them.
You must use the glob function for wildcard processing. BTW, the shell programs use it...
I'm currently working on my own replica of the history builtin.
I realize I need the HISTSIZE, HISTFILE and HISTFILESIZE variables to achieve the command options but I don't know how to get them. Is there a function similar to getenv or a variable like environ but with the local shell variables ?
Thanks in advance !
Well a builtin is executed directly by the shell for 2 possible reasons:
you avoid spawning a new processus (performance reason)
it is the only way to have access to internal shell variables (your use case)
A shell could only provide access to its internal variables by two ways:
either copy them to the environment because by definition the environment is the list of variables that are passed to child processes
either provided an inter process communication mechanism (named pipe, private sockect, etc.) to allow its childs to ask them
AFAIK, no common shell implement either, but you can export the required shell variables to the environment:
export HIST
export HISTSIZE
export HISTFILESIZE
should be enough to later get them through getenv
The main declaration can have an extra argument, char **envp, which is an 2D array of char terminated by a NULL pointer. Here is an example :
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
int index = 0;
while (envp[index])
printf("%s\n", envp[index++];
return 0;
}
EDIT
Using envp will provide you to get the environnement. To find shell variables, I found this. I think that could help you.
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("SET = %s\n",getenv("SET"));
return 0;
}
To test Program, set the variable first and then execute :-
export SET=1
As Steffan Hegny pointed out, there is no way to acces these variables if they are no environment variables. So popen(), what I thought could help, isn't a solution to your problem.
However if you need acces to those variables you can export their values to a real environment variable which can be accessed via getenv(char*).
E.g.
export _HISTFILE=$HISTFILE && ./myProgram
or
_HISTFILE=$HISTFILE ./myProgram
with myProgram accessing the value like this:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("History file: %s", getenv("_HISTFILE"));
}
I'm using GTK to create an interface for my C program running Linux.
I'm using this function to load my XML interface:
gtk_builder_add_from_file(builder, g_build_filename("myInterface.glade", NULL), &error);
As long as I'm in the same directory as my compiled file, it works.
But if I'm in another directory, let say a child one and I execute it: ../a.out, my program doesn't find the file.
So the idea is to first get the program path ("/home/.../program") and to add the file name to get an absolute path.
But I have no idea how can I get the program path. And I'm not sure this is the best idea to make it work.
Any idea? Thanks!
argv[0] contain the relative/full path you ran to run the program.
just scan up to the last '/' and this will be the run dir from your current location
'edit' after some more research, i found this, which works in all cases:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <libgen.h>
int main()
{
char path[500] = {0};
int dest_len = 500;
if (readlink ("/proc/self/exe", path, dest_len) != -1)
{
dirname (path);
strcat (path, "/");
printf("path=%s\n", path);
}
}
In your case where you are using GTK, it is better to use GResource and compile myInterface.glade directly into your program.
I'm writing a C program on Linux and need to execute a command with system(), and need to set an environment variable when executing that command, but I don't know how to set the env var when using system().
If you want to pass an environment variable to your child process that is different from the parent, you can use a combination of getenv and setenv. Say, you want to pass a different PATH to your child:
#include <stdlib.h>
#include <string.h>
int main() {
char *oldenv = strdup(getenv("PATH")); // Make a copy of your PATH
setenv("PATH", "hello", 1); // Overwrite it
system("echo $PATH"); // Outputs "hello"
setenv("PATH", oldenv, 1); // Restore old PATH
free(oldenv); // Don't forget to free!
system("echo $PATH"); // Outputs your actual PATH
}
Otherwise, if you're just creating a new environment variable, you can use a combination of setenv and unsetenv, like this:
int main() {
setenv("SOMEVAR", "hello", 1); // Create environment variable
system("echo $SOMEVAR"); // Outputs "hello"
unsetenv("SOMEVAR"); // Clear that variable (optional)
}
And don't forget to check for error codes, of course.
This should work:
#include "stdio.h"
int main()
{
system("EXAMPLE=test env|grep EXAMPLE");
}
outputs
EXAMPLE=test
Use setenv() api for setting environment variables in Linux
#include <stdlib.h>
int setenv(const char *envname, const char *envval, int overwrite);
Refer to http://www.manpagez.com/man/3/setenv/ for more information.
After setting environment variables using setenv() use system() to execute any command.
How do I transfer files from one folder to another, where both folders are present in oracle home directory?
int main(int argc, char *argv[]){
char *home, *tmp2;
home = getenv("ORACLE_HOME");
temp2 = getenv("ORACLE_HOME");
strcat (home,"A");
strcat (tmp2,"B");
//transfer files from home to tmp2
}
strcat doesn't seem to work. Here, I see tmp2 pointer doesn't get updated correctly.
Edit: OS is a UNIX based machine. Code edited.
I require a binary file which does this copying, with the intention that the real code cannot be viewed. Hence I didn't consider using shell script as an option. The files in A are encrypted and then copied to B, decrypted in B and run. As the files are in perl, I intend to use system command to run them in the same C code.
Using the system(3) command is probably a good idea since you get the convenience of a shell interpreter to expand filenames (via *) but avoids the hassle of computing the exact length of buffer needed to print the command by using a fixed length buffer and ensuring it cannot overflow:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUFSZ 0xFFF
int main(void)
{
char * ohome = getenv("ORACLE_HOME"), cmd[BUFSZ];
char * fmt="/bin/mv %s/%s/* %s/%s";
int written = snprintf(cmd, BUFSZ, fmt, ohome, "A", ohome, "B"), ret;
if ((written < 0) || (written >= (BUFSZ-1))) {
/* ERROR: print error or ORACLE_HOME env var too long for BUFSZ. */
}
if ((ret = system(cmd)) == 0) {
/* OK, move succeeded. */
}
return 0;
}
As commenter Paul Kuliniewicz points out, unexpected results may ensue if your ORACLE_HOME contains spaces or other special characters which may be interpreted by the subshell in the "system" command. Using one of the execl or execv family will let you build the arguments without worrying about the shell interpreter doing it's own interpretation but at the expense of using wildcards.
First of all as pointed out before, this "security" of yours is completely useless. It is trivial to intercept the files being copied (there are plenty of tools to monitor file system changes and such), but that is another story.
This is how you could do it, for the first part. To do the actual copying, you'd have to either use system() or read the whole file and then write it again, which is kind of long for this kind of quick copy.
int main(int argc, char *argv[]){
char *home, *tmp2;
home = strdup(getenv("ORACLE_HOME"));
tmp2 = strdup(getenv("ORACLE_HOME"));
home = realloc(home, strlen(home)+strlen("A")+1);
tmp2 = realloc(tmp2, strlen(tmp2)+strlen("B")+1);
strcat (home,"A");
strcat (tmp2,"B");
}
By the way, if you could stand just moving the file, it would be much easier, you could just do:
rename(home,tmp2);
Not realted to what you are asking, but a comment on your code:
You probably won't be able to strcat to the results of a getenv, because getenv might (in some environments) return a pointer to read-only memory. Instead, make a new buffer and strcpy the results of the getenv into it, and then strcat the rest of the file name.
The quick-n-dirty way to do the transferring is to use the cp shell command to do the copying, but invoke it using the system command instead of using a shell script.
Or, have your C program create a shell script to do the copying, run the shell script, and then delete it.