I am reading the book Windows System Programming. In the second chapter, there is a program Cat.c. It implements the cat command of linux. The code is http://pastebin.com/wwQFp599
Here is the part in which confuses me:
/* iFirstFile is the argv [] index of the first input file. */
iFirstFile = Options (argc, argv, _T("s"), &dashS, NULL);
if (iFirstFile == argc) { /* No files in arg list. */
CatFile (hStdIn, hStdOut);
return 0;
}
As mentioned in the comment iFirstFile is the argv [] index of the first input file.
It means if I try cat -s abc.txt, then iFirstFile = 2, but argc == 3.
I can't think in what circumstance iFirstFile == argc ? I can't understand the logic behind it. Can anyone explain me this part?
Like it says in the comments, if there are no filenames in argv, then Options() returns argc. So this is the case where you want to cat stdin and not a file.
if (iFirstFile == argc) { /* No files in arg list. */
CatFile (hStdIn, hStdOut);
return 0;
}
For example "cat > x" reads from the stdin. So does "foo | cat | bar". In each of these cases Options() would return argc;
If you run the program with:
cat
Then argc == 1 and iFirstFile == 1. Therefore the if statement condition will be true and it will run using stdin and stdout, allowing the use of pipes or interactive input, or output to the terminal window.
Related
So the following snippet should either make the prompt in a simple shell program 'my257sh'>, or ''> if -p is used as a command line arg when launching, followed by a string to be used as the custom prompt.
The custom prompt works fine, but I get a seg fault when launching with no additional args.
Can someone tell me what incredibly simple and stupid thing I'm doing wrong?
int main(int argc, char *argv[]) {
char cmdline[MAXLINE]; /* Command line */
char def_prompt[20] = "my257sh";
const char prompt_check[2] = "-p";
char new_prompt[20];
if (argc > 0){
if (strstr(argv[1], prompt_check)) {
strcpy(new_prompt, argv[2]);
}
}
else {
strcpy(new_prompt, def_prompt);
}
signal(SIGINT, sigIntHandler); // ignores ctrl + C interrupt
while (1) {
/* Read */
printf("%s> ",new_prompt);
...
argc is always > 0 : argv[0] contains the name of the program.
Check
If argc > 1
Also in case of -p check that
If argc > 2
You also need to check the length of prompt to avoid copying in too small string
argc contains the number of arguments passed on the shell, including the command itself, so argc will always be > 0 as it is at least 1 if no parameters were given.
If you don't pass any arguments, then argv[1] will contain a NULLpointer. If you pass only one parameter then argv[2] is a NULL pointer.
if (argc > 2)
{
if (strstr(argv[1], prompt_check))
strcpy(new_prompt, argv[2]);
}
else
{
strcpy(new_prompt, def_prompt);
}
Im programming a shell. This is a small piece of it that handles redirects. I only included the snippet that is causing the error. This portion of the program is supposed to take a command/argument line and execute the line accordingly. The wc < junk > junk2 is an example of a command line input and for some reason when i execute the line its producing that error junk: >: open: No such file or directory. The rest of the program functions fine but its breaking here.
: wc < junk > junk2
junk: >: open: No such file or directory
1 4 31 junk2
1 4 31 total
I briefly had it working as intended but for some reason I must have lost a piece of the puzzle because it's broken once again.
Ive looked at my array values and they are as expected. For some clarification I'm using a copy of the arguments array so the original is unchanged by setting the argument copy value to NULL. Im setting the argument copy value to null so the loop doesn't repeatedly use a redirection.
If you have any advise or see the problem that I apparently can't it would be appreciated.
while (arguments[i] != NULL)
{
if ((strcmp(argumentsCopy[i], "<") == 0))
{
if ((access(argumentsCopy[i + 1], R_OK) == -1))
{
printf("Cannot open %s for input\n", argumentsCopy[i + 1]);
fflush(stdout);
redirect = 1;
}
else
{
int fd = open(argumentsCopy[i + 1], O_RDONLY);
dup2(fd, STDIN_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1;
execvp(command, &arguments[i + 1]);
}
}
if ((strcmp(argumentsCopy[i], ">") == 0))
{
int fd = open(argumentsCopy[ i + 1], O_RDWR);
dup2(fd, STDOUT_FILENO);
close(fd);
argumentsCopy[i] = NULL;
redirect = 1; // avoid repeat use of redirection symbol in arguments array
execvp(command, &arguments[i + 1]);
}
i++;
}
if (redirect == 0)
{
execvp(command, execArgs); //execArgs is entire command w/ arguments
}
exit 0; //default if error occurred.
First, note that you're not correctly using the execvp function. Once a call to execvp (or any call in the exec family) succeeds, the execution of the current program terminates and is replaced by the program you are execing. Thus, control flow should never proceed past an execvp call unless that call failed. Therefore, you cannot call execvp until you are sure both the stdin and stdout file descriptors have been properly redirected (in the case that you have both input and output redirection). From your code sample, it appears you call execvp once either one is detected, which means your shell can only redirect input or output, not both.
However, that is not the source of the error you're receiving. Consider the input wc < junk > junk2. Based on the information you provided, the command and arguments array will be populated as follows:
command = "wc"
arguments[0] = ">"
arguments[1] = "junk"
arguments[2] = "<"
arguments[3] = "junk2"
arguments[4] = NULL
When i = 0, the first if-statement will be taken, opening the file descriptor and performing execvp with the parameters command and &arguments[i+1]. These correspond to the file to be executed and the argv array passed to that function's main method respectively. In this case, the command is wc and the args are { "junk", "<", "junk2", NULL } (as this is the value of the null terminated array beginning at &arguments[i+1]). On Unix systems, it is conventional for the first argument (argv[0]) to be the name of the current program. Thus the actual command line arguments begin at argv[1] (see Program Arguments for documentation). From wc's perspective, the command line arguments it should process are { "<", "junk2", NULL } after it processes what it believes to be its program name, argv[0], or junk.
wc takes as arguments the list of files it will process. In this case, it believes that list to be < and junk2, as "<" is an operator recognized by bash and other shells, not the executing program. Thus, wc (which believes its name is junk because of argv[0]) attempts to open < as an input file and fails, printing the message:
junk: >: open: No such file or directory
I'm making a part of a bigger program, this part takes in information from a text file or a feed of standard input, the nature of this information depends on what option is added to the program. With no text file added as a non-option argument, the program should read from stdin.
For example:
fizzle.exe -a textfile.txt reads textfile.exe in the way prescriped by the option -a
fizzle.exe -a reads stdin in the way prescribed by the option a
What I havent gotten to work yet is:
fizzle.exe textfile.txt reading textfile.txt(or stdin if no proper text file) in the way prescribed by giving no option
This is my code:
while ((option = getopt(argc,argv, ":a:b:")) != -1 ) {
FILE *pointerFile = filereader(optarg);
switch(option) {
case 'a' :
optionchosen = 0;
counter(optionchosen,pointerFile,argv);
break;
case 'b' :
optionchosen = 1;
counter(optionchosen,pointerFile,argv);
break;
case ':' :
counter(optionchosen,pointerFile,argv);
break;
}
}
I cant figure out how to add a case to this switch that is activated by giving no option, but still works with or without the non-option argument(filename).
Well, you can do exactly what you want to do with getopt, and more, you just need to adjust your logic slightly. Specifically, you can setup getopt so that:
fizzle.exe /* reads from stdin */
fizzle.exe -a /* reads from stdin */
fizzle.exe -a textfile.txt /* reads textfile.txt */
fizzle.exe textfile.txt /* reads textfile.txt */
Here is a short example of implementing the above logic around the -a option. The last case also specifies that, if give, the first unhandled option after all options are given will also be taken as the filename to read.
#include <stdio.h>
#include <unistd.h> /* for getopt */
int main (int argc, char **argv) {
int opt;
char *fn = NULL;
FILE *pointerFile = stdin; /* set 'stdin' by default */
while ((opt = getopt (argc, argv, "a::b:")) != -1) {
switch (opt) {
case 'a' : /* open file if given following -a on command line */
if (!optarg) break; /* if nothing after -a, keep stdin */
fn = argv[optind - 1];
pointerFile = fopen (fn, "r");
break;
case 'b' :; /* do whatever */
break;
default : /* ? */
fprintf (stderr, "\nerror: invalid or missing option.\n");
}
}
/* handle any arguments that remain from optind -> argc
* for example if 'fizzle.exe textfile.txt' given, read from
* textfile.txt instead of stdin.
*/
if (pointerFile == stdin && argc > optind) {
fn = argv[optind++];
pointerFile = fopen (fn, "r");
}
printf ("\n fizzle.txt reads : %s\n\n", pointerFile == stdin ? "stdin" : fn);
return 0;
}
Example Use/output
$ ./bin/optfizzle
fizzle.exe reads : stdin
$ ./bin/optfizzle -a
fizzle.exe reads : stdin
$ ./bin/optfizzle -a somefile.txt
fizzle.exe reads : somefile.txt
$ ./bin/optfizzle someotherfile.txt
fizzle.exe reads : someotherfile.txt
Look it over and let me know if you have any questions.
Forget the leading : in your getopt string; just use getopt for options. After you're done with them, the remaining elements on the command line, if any, are found between argv[optind] and argv[argc].
If the caller provides no filename, use /dev/stdin as the default, or stdin from stdlib.h. No need to require the user to provide a - to stand in for standard input.
Programs using getopt should, by convention, read from stdin if you give it a - as an input parameter.
As a side note, you should always have a case for ? (or a default case).
Here is an example of reading the remaining arguments, place it below the big while loop where you process all getopt flags:
int i;
for (i = optind; i < argc; ++i)
printf("Non-option argument %s\n", argv[i]);
I am trying to understand how to use command line options with a command line c tool and I came accross this example.Can some one explain how the code flow works,I am not able to understand it,also I understand that it uses a getopt() function which is inbuilt.
The exe called is rocket_to and it has two command line options, e and a. e option takes 4 as an argument and a option takes Brasalia,Tokyo,London as argument.
Can some one explain how the code works?
This is the actual code:
command line:
rocket_to -e 4 -a Brasalia Tokyo London
code:
#include<unistd.h>
..
while((ch=getopt(argc,argv,"ae:"))!=EOF)
switch(ch){
..
case 'e':
engine_count=optarg;
..
}
argc -=optind;
argv +=optind;
There are many manual pages for getopt() including the POSIX specification. They describe what the getopt() function does. You can also read the POSIX Utility Conventions which describes how arguments are handled by most programs (but there are plenty of exceptions to the rules, usually because of historical, pre-POSIX precedent).
In the example outline code, the -e option takes an argument, and that is the 4 in the example command line. You can tell because of the e: in the third argument to getopt() (the colon following the letter indicates that the option takes an argument). The -a option takes no argument; you can tell because it is not followed by a colon in the third argument to getopt(). The names Brasilia, Tokyo, London are non-option arguments after the option processing is complete. They're the values in argv[0] .. argv[argc-1] after the two compound assignments outside the loop.
The use of EOF is incorrect; getopt() returns -1 when there are no more options for it to process. You don't have to include <stdio.h> to be able to use getopt().
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int ch;
int aflag = 0;
char *engine_count = "0";
while ((ch = getopt(argc, argv, "ae:")) != -1)
{
switch (ch)
{
case 'a':
aflag = 1;
break;
case 'e':
engine_count = optarg;
break;
default:
fprintf(stderr, "Usage: %s [-a][-e engine] [name ...]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
argc -= optind;
argv += optind;
printf("A flag = %d\n", aflag);
printf("Engine = %s\n", engine_count);
for (int i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
return 0;
}
That is working code which, if compiled to create a program rocket_to, produces:
$ ./rocket_to -e 4 -a Brasilia Tokyo London
A flag = 1
Engine = 4
argv[0] = Brasilia
argv[1] = Tokyo
argv[2] = London
$ ./rocket_to -a -e 4 Brasilia Tokyo London
A flag = 1
Engine = 4
argv[0] = Brasilia
argv[1] = Tokyo
argv[2] = London
$ ./rocket_to -e -a 4 Brasilia Tokyo London
A flag = 0
Engine = -a
argv[0] = 4
argv[1] = Brasilia
argv[2] = Tokyo
argv[3] = London
$
From the getopt man page:
The getopt() function parses the command-line arguments. Its arguments argc and argv are the argument count and array as passed to
the main() function on program invocation. An element of argv that starts with '-' (and is not exactly "-" or "--") is an option element. The characters of this element (aside from the initial '-') are option characters. If getopt() is called repeatedly, it
returns successively each of the option characters from each of the option elements.
The 3rd argument to getopt() are the valid options. If the option is followed by a colon it requires an argument. The argument can be accessed through the optarg variable. So in your example you have two options: 'a' which takes no argument and 'e' which takes an argument.
If getopt() finds an options it returns the character. If all options are parsed it returns -1 and if an unknown option is found it returns -1.
So your code loops through all options and processes them in a switch statement.
Next time when you have trouble understanding something like this try to run man <unknown function> first.
I'm designing a simple shell but I have a problem with advanced redirection.
I am able to do this : ls -al > a.txt
But i couldn't do this : wc < a.txt > b.txt
How can i do that?
Here is where i perform my i/o redirection :
char *inpu=NULL; //Inpu is a global variable.
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_TRUNC)
#define CREATE_FLAGS1 (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_FLAGS2 (O_RDONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define MAXCHARNUM 128
#define MAXARGNUM 32
char *argsexec[MAXARGNUM]; /*This stores my executable arguments like cd ls.*/
void ioredirection(int f){
int k,i,m;
int input=-1;
int output=-1;
int append=-1;
int fdin,fdout;
for(k=0; k<f; k++){
if(strcmp(argsexec[k],"<")==0){
input=k; // argument place of "<"
m=1;
argsexec[k]=NULL;
}
else if(strcmp(argsexec[k],">")==0){
output=k; // argument place of ">"
m=2;
argsexec[k]=NULL;
}
else if(strcmp(argsexec[k],">>")==0){
append=k; // argument place of ">>"
m=3;
argsexec[k]=NULL;
}
}
if(m==1){
int inp1;
fdin=open(argsexec[input+1],O_RDONLY,CREATE_MODE);
dup2(fdin,STDIN_FILENO);
close(fdin);
inp1=execlp(argsexec[0],argsexec[0],NULL);
}
if(m==2){
fdout = open(argsexec[output+1], CREATE_FLAGS, CREATE_MODE);
dup2 (fdout, STDOUT_FILENO);
close(fdout);
execvp(argsexec[0],argsexec);
}
if(m==3){
fdout = open(argsexec[append+1], CREATE_FLAGS1, CREATE_MODE) ;
dup2 (fdout, STDOUT_FILENO);
close(fdout);
execvp(argsexec[0],argsexec);
}
}
b is changing like this :
inpu=strtok(str," ");
while(inpu!=NULL){
argsexec[b]=inpu;
b++;
inpu = strtok(NULL, " ");
}
And i'm calling it with child process.
if (pid==0){
ioredirection(b);
I hope it is clear to understand, my full is code really long i tried to cut it like this. Any suggestion will be appreciated.
I ran your code through a formatter and got (greatly simplified):
void ioredirection(int f) {
for (k over all arguments) {
set m based on argsexec[k]
}
test m against 1, 2, and 3
}
Thus, if there are two (or more) "re-direction" operations, the loop (for k) will set m two (or more) times. Then, the loop having terminated, you (finally) test m once against each of three possible values.
The first problem should now be clear (but since this is a school project I'm not going to solve it for you :-) ).
The second problem is clear only in looking at the three tests performed on m. Looking at just one will suffice here:
if (m == 1) {
int inp1;
fdin = open(argsexec[input + 1], O_RDONLY, CREATE_MODE);
dup2(fdin, STDIN_FILENO);
close(fdin);
inp1 = execlp(argsexec[0], argsexec[0], NULL);
}
(The other two use execvp rather than execlp.1) If an exec* family function call succeeds, the current process will be replaced immediately, so that the exec* never returns. So if you need to redirect two (or more) *_FILENO values, the eventual exec* call has to be put off until after all other redirections are also done.
1One of these is not the appropriate function. OK, no dancing around here: execvp is the proper choice. :-)
A third problem occurs if a redirect is not followed by a file name (see below).
The last two potential problems with this code snippet is much less obvious and needs looking at the whole thing. Whether one is a "real bug" depends on how simple your shell is meant to be.
The argsexec array can hold up to 128 char * pointer values. The entry in argsexec[0] should be the name of the binary to run—it's supplied to execvp after all—and to use execvp, argsexec[0] must be the first of however many char *s in sequence:
argsexec[0] becomes argv[0] (in the program you invoke),
argsexec[1] becomes argv[1] (the program's first argument),
argsexec[2] becomes argv[2],
and so on ... until:
argsexec[i], for the smallest integer i, is NULL: and that tells the execv* family of functions: "OK, you can stop copying now."
Let's skip over definite bug #3 for a bit longer, and talk about potential bug #4: It's not clear here whether any argsexec[i] has been set to NULL. The argument f to iodirection is the last i for which argsexec[i] must not be NULL; we can't tell, from this code fragment, whether some (presumably the f+1th) argsexec[i] is NULL. To use the execvp function, it will need to be NULL.
If there are some I/O redirections, you will set some argsexec[i] to NULL. That will terminate the array and make the execvp call work correctly. If not ... ?
This leads to potential bug #5: In "real" Unix shells you can place I/O redirections "in the middle" of a command:
prog > stdout arg1 2> stderr arg2 < stdin arg3
This runs prog with three arguments (arg1, arg2, and arg3). In fact, you can even put the redirections first:
<stdin >stdout 2>stderr prog arg1 arg2 arg3
and in some shells you can "re-direct" more than once (and then not even bother running a command, if you don't want one):
$ ls
$ > foo > bar > baz
$ ls
bar baz foo
(but other shells forbid it:
$ rm *; exec csh -f
% > foo > bar > baz
Ambiguous output redirect.
not that csh is anything to emulate. :-) )
If—this is a big "if"—you want to allow this or something similar, you'll need to "execute and remove" each I/O redirection as it appears, moving remaining arguments down so that argsexec remains "densely populated" with the various char * values that are to be supplied to the program. For instance, if len is the length of the valid, non-NULL entries in the array, so that argsexec[len] is NULL, and you need to "remove" argsexec[j] and argsexec[j + 1] (which contain a redirection like ">", and a file name, respectively), then:
for (i = j + 2; i <= len; i++) {
argsexec[i - 2] = argsexec[i];
}
would do the trick (the loop runs to i <= len so that it copies the terminating NULL as well).
So, finally, definite bug #3: what happens if a redirect is at the very last position, argsexec[f - 1]? The for (k ...) loop runs k from 0 to f - 1 inclusive. If, when k == f - 1, argsexec[k] is a redirect, the file name must be in argsexec[f].
But we just noted (above) that argsexec[f] needs to be NULL. That is, if someone tries:
ls >
then argsexec[] should contain "ls", ">", and NULL.
Here's what the "real shells" sh and csh do in that case:
$ ls >
Syntax error: newline unexpected
% ls >
Missing name for redirect.
You'll need something similar: a way to reject the attempt, if the file name after the redirection is missing.