How does shell expands *.c? - c

I encounter a question while I am reading a textbook - Unix System Programming
How big is the argument array passed as the second argument to execvp
when you execute execcmd of Program 3.5 with the following command
line?
execcmd ls -l *.c
Answer: The answer depends on the number of .c files in the current
directory because the shell expands *.c before passing the command
line to execcmd.
Program 3.5:
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "restart.h"
int main(int argc, char *argv[]) {
pid_t childpid;
if (argc < 2){ /* check for valid number of command-line arguments */
fprintf (stderr, "Usage: %s command arg1 arg2 ...\n", argv[0]);
return 1;
}
childpid = fork();
if (childpid == -1) {
perror("Failed to fork");
return 1;
}
if (childpid == 0) {
execvp(argv[1], &argv[1]);
perror("Child failed to execvp the command");
return 1;
}
if (childpid != r_wait(NULL)) {
perror("Parent failed to wait");
return 1;
}
return 0;
}
Why is the size of argument array passed depending on the number of .c files in the current directory? Isn't it the argument array just something like
argv[0] = "execcmd";
argv[1] = "ls";
argv[2] = "-l";
argv[3] = "*.c";
argv[4] = NULL;
Update: Find a link explains pretty well about the shell expansion. May useful for someone who see this post later also do not understand shell expansion.
Description about shell expansion

No, because the shell does the wild card expansion.
It finds files in the directory that match the expression, so for instance you can use "echo *.c" to discover what the shell would match. Then it lists out, every filename matching *.c on the exec call or if none *.c which is likely to result in an error message about file not found.
It is more powerful that the shell does the expansion, the same file wildcarding is immediately available for all programs, like cat, echo, ls, cc.

Related

execv vs execvp, why just one of them require the exact file's path?

I have two files in the same directory.
directory/
| a.c
| b.c
a.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
int status;
int wret;
if ((pid = fork()) < 0)
printf("error");
else if(pid == 0)
{
printf("%s", argv[1]);
execv(argv[1], &argv[1]);
}
else
{
/* respawn */
if ((wret = wait(&status)) != -1)
execv(argv[1], &argv[1]);
}
return 0;
}
b.c is just a simple program that print "hello".
I want to run ./a b from the command line to make the a program call exexXX to execute the b program.
I don't understand why if I use execv I can write just ./a b in the command line, instead if I use execvp I have to write ./a ./b.
The man exec page is not clear because it reports
"The initial argument for these functions is the name of a file that
is to be executed."
Thanks
If the program name argument contains no slashes, the execvp() function looks for the program to execute in the directories listed on your PATH environment variable. If you don't have . (the current directory) on your PATH and you aren't in one of the directories listed on your path, a plain name like b will not be executed, even if b is in the current directory. If the name contains a slash, it can be relative (./b) or absolute (/home/someone/src/programs/b) and it will be interpreted as a file name to be executed without consulting the PATH environment variable.
By contrast, execv() treats a plain b in the program name argument as ./b — the name of the file in the current directory and executes it if it is present, and fails if it is located somewhere else.
At one time, there was a comment that asked:
Are you saying if you have an executable b in . and you do execv("b", b_args), it will get executed?
On a normal Unix box, yes.
Code b.c:
#include <stdio.h>
int main(void)
{
puts("Hello");
return 0;
}
Code a.c:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char *argv[] = { "b", 0 };
execv(argv[0], argv);
fprintf(stderr, "failed to execute '%s'\n", argv[0]);
return 1;
}
Running these:
$ (PATH=$(clnpath "$PATH" ".:$PWD"); echopath PATH; ./a)
/Users/jleffler/bin
/opt/informix/12.10.FC6/bin
/Users/jleffler/oss/bin
/Users/jleffler/oss/rcs/bin
/usr/local/mysql/bin
/opt/gcc/v7.3.0/bin
/Users/jleffler/perl/v5.24.0/bin
/usr/local/bin
/usr/bin
/bin
/opt/gnu/bin
/usr/sbin
/sbin
Hello
$
The clnpath script modifies the string provided as its first argument ("$PATH") by removing any occurrences of any of the directory names listed in its second path-like argument (".:$PWD") — it's how I edit my PATH on the fly when I need to. The echopath script echoes the directories on PATH (or any other path-like variable, or it will process the result of expanding a pathlike variable, such as "$PATH"), one per line — the output shows that neither . nor /Users/jleffler/soq (which is where I run the program) is on $PATH in the sub-shell. The ./a runs the code from a.c (it would not be executed without that ./ in front), which in turn runs the code from b.c, which produces the Hello. (If there is some system where this does not work, please identify it.)
I could also arrange for b.c to be:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
puts("Hello");
const char *env = "PATH";
char *val = getenv(env);
if (val == 0)
val = "<nothing>";
printf("%s=%s\n", env, val);
return 0;
}
which would print the value of $PATH directly from the executable (to verify that neither . nor the value of the current working directory is listed).

How can I execute a Bash program from C? [duplicate]

This question already has an answer here:
Is it possible to include a shell script in a C program
(1 answer)
Closed 6 years ago.
How can I execute a command with bash? system() uses sh and not bash.
I know that I can execute commands in bash with system("/bin/bash -c command"). But I have a very long command and /bin/bash -c gives me problems. What I need is bashrun(command) or something else.
The command is a string, not a file
Case1 : script from a file - Use the shebang
#!/usr/bin/env bash
at the top of your script and then do
int status=system("/full/path/to/script");
if(status==-1){
// failure mode
}
Case2 : script stored as a string
Do something like below
char *command="$(which bash) -c 'ls'";
int status=system(command);
if (status==-1){
//failure mode
}
If you're creating a very long shell command and you need bash to interpret it, then you have two real options:
Save the text into a file and invoke bash with the filename as a single argument (equivalently, use a shebang in the file to specify bash as the interpreter, make the file executable, and invoke that as command), or
Start an instance of bash with popen() and write the shell command as standard input to the bash process.
If you're having problems due to shell script quoting (rather than the command length), then either of those options would work, or you could implement the equivalent of system() but using execl() to pass the argument without going through sh. I'm assuming a POSIX-type system here.
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
int run_bash(const char *command)
{
int pid = fork();
if (pid < 0) {
/* failed */
perror("fork");
return pid;
} else if (pid == 0) {
/* child */
execl("/bin/bash", "bash", "-c", command, (char*)NULL);
perror("exec");
exit(EXIT_FAILURE);
} else {
/* parent */
int status;
do {
waitpid(pid, &status, 0);
} while (!WIFEXITED(status));
return WEXITSTATUS(status);
}
}
int main(void)
{
run_bash("echo '*'");
}

Unable to identify behaviour of execl() function call

I was working on my project when I needed to use "curl" to obtain some data from www. Now firstly I tried direct system() function but it didn't worked, strangely everytime it corrupted the whole source code file while compiling with gcc. Luckily I was testing it separately.
Then I tested execl() function, this code compiles OK and gcc gives me a .exe file to run, but nothing happens when I run it,blank windows appears. CODE:
int main(){
execl("curl","curl","http://livechat.rediff.com/sports/score/score.txt",">blahblah.txt",NULL);
getch();
return 0;
}
Includes are not shown properly but I have included stdio,conio,stdlib and unistd.h.
How can I get output of program to store in text file? Also running the above command creates and stores text file in My Documents, I want it to be in local directory from where I run the program. How can I do that?
You need to provide the path of curl, and you cannot use redirection because the application will not be executed through bash. Instead use the -o flag and specify the filename. Also, execl does not return when successful:
#include <unistd.h>
#include <stdio.h>
int main(){
execl("/usr/bin/curl",
"curl","http://livechat.rediff.com/sports/score/score.txt",
"-oblahblah.txt",NULL
);
printf("error\n");
return 0;
}
If you want your code to return, you should fork a child process to run the command. This way you can check the return code.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define CURL "/usr/bin/curl"
int main()
{
pid_t pid;
int status;
pid = fork();
if (pid == 0)
{
execl(CURL, CURL, arg1, NULL);
}
else if (pid < 0)
{
printf("Fork failed\n");
exit (1);
}
else
{
if (waitpid(pid, &status, 0) != pid)
status = -1;
}
return status;
}
arg1 is whatever argument you want to use with curl or if you aren't using any than you obviously can omit it.

How to run this program?

I can compile this program which was provided to me, but that I must further develop. I have some questions about it:
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define TIMEOUT (20)
int main(int argc, char *argv[])
{
pid_t pid;
if(argc > 1 && strncmp(argv[1], "-help", strlen(argv[1])) == 0)
{
fprintf(stderr, "Usage: RunSafe Prog [CommandLineArgs]\n\nRunSafe takes as arguments:\nthe program to be run (Prog) and its command line arguments (CommandLineArgs) (if any)\n\nRunSafe will execute Prog with its command line arguments and\nterminate it and any remaining childprocesses after %d seconds\n", TIMEOUT);
exit(0);
}
if((pid = fork()) == 0) /* Fork off child */
{
execvp(argv[1], argv+1);
fprintf(stderr,"RunSafe failed to execute: %s\n",argv[1]);
perror("Reason");
kill(getppid(),SIGKILL); /* kill waiting parent */
exit(errno); /* execvp failed, no child - exit immediately */
}
else if(pid != -1)
{
sleep(TIMEOUT);
if(kill(0,0) == 0) /* are there processes left? */
{
fprintf(stderr,"\nRunSafe: Attempting to kill remaining (child) processes\n");
kill(0, SIGKILL); /* send SIGKILL to all child processes */
}
}
else
{
fprintf(stderr,"RunSafe failed to fork off child process\n");
perror("Reason");
}
}
What does my warning mean when I compile it?
$ gcc -o RunSafe RunSafe.c -lm
RunSafe.c: In function ‘main’:
RunSafe.c:30:44: warning: incompatible implicit declaration of built-in function ‘strlen’ [enabled by default]
Why can't I execute the file?
$ file RunSafe
RunSafe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x0a128c8d71e16bfde4dbc316bdc329e4860a195f, not stripped
ubuntu#ubuntu:/media/Lexar$ sudo chmod 777 RunSafe
ubuntu#ubuntu:/media/Lexar$ ./RunSafe
bash: ./RunSafe: Permission denied
ubuntu#ubuntu:/media/Lexar$ sudo ./RunSafe
sudo: ./RunSafe: command not found
First, you need to #include <string.h> to get rid of that warning.
Second, the OS is probably preventing you from executing programs on the /media/Lexar filesystem, no matter what their permission bits are. If you type mount you'll probably see the noexec option for /media/Lexar.
warning: incompatible implicit declaration of built-in function ‘strlen’ [enabled by default]
You need to include #include<string.h> because strlen() is declared in it.
Try running the exe on some other location in your filesystem and not the mounted partition as the error indicates for some reason you don't have permissions on that mounted partition.

exec() not working with firefox

I've been using a combination of fork() and exec() to execute some external command on linux, however, the code seems to fail whenever I try to execute /usr/bin/firefox which is a symbolic link to a real binary.
Does anyone know how to solve this problem? I've tested with other programs (which really are executable binaries and not symlinks to them) and it works.
Here's the code from the program:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv) {
pid_t pid;
// this was the old line:
// char *parmList[] = {"", "index.html", NULL};
// and this is the one that solves the problem:
char *parmList[] = {"firefox", "index.html", NULL};
int a;
if ((pid = fork()) == -1)
perror("fork failed");
if (pid == 0) {
a = execvp("/usr/bin/firefox", parmList);
fprintf(stdout, "execvp() returned %d\n", a);
fprintf(stdout, "errno: %s (%d).\n", strerror(errno), errno);
}
else {
waitpid(pid, 0, 0);
}
return 0;
}
Edit: I updated the code to include the answer and changed the topic's title because the problem really didn't seem to be due to symbolic links at all. Thanks everyone.
You might want to add some code right after the execvp to output some diagnostic (i.e. check errno, print something meaningful ;)).
You could also try to analyze it w/o source modification using strace or gdb for that matter.
See also: execve.
Update as follow-up from the comments
Firefox is not happy with argv[0] being empty, which is what argList looked like, unfortunately.
Lessons learned: Be thoroughly aware of what you pass as argv to the program you execute. :)
Does Firefox insist on having a non-empty argv[0]? You should normally pass the name of the command (either just "firefox" or "/usr/bin/firefox") to the command, but you are not doing so.
[...going to check the deeper comments above - and it seems this is the correct diagnosis, but 21 minutes or so late...]

Resources