"more" as a target of piped command breaks bash - c

Consider following source, reduced for simplicity
int main()
{
int d[2];
pipe(d);
if(fork())
{
close(1);
dup(d[1]);
execlp("ls", "ls", NULL);
}
else
{
close(0);
dup(d[0]);
execlp("cat", "cat", NULL);
}
}
So it creates a pipe and redirects the output from ls to cat.
It works perfectly fine, no problems. But change cat to more and bash breaks.
The symptoms are:
you don't see anything you type
pressing "enter" shows up a new prompt, but not in a new line, but in the same one
you can execute any command and see the output
reset helps fixing things up.
So there is a problem with input from keyboard, it is there, but is not visible.
Why is that?
UPDATE:
the output from ls | more is equivalent to the output of my program
more process does not finish, it's is orphaned by ls
the only visible problem is with the state of the console after the parent process quits
on some systems it does work like intended. E.g., on OpenSUSE I had no problems, on Kubuntu. I couldn't find any information on what differences should I look for, more binaries are different on both systems

Because unlike cat, more is an interactive program that requires more than stdin, stdout and stderr -- it requires a terminal, which your system call cannot provide. Try to run more in a pipe or from a script and see what happens.
Also note that bash is not involved here at any stage, the execlp function call replaces the current process by another one specified as an argument.

Related

Is there a way to redirect stderr to a file that works in bash, csh and dash?

How do I redirect stderr (or stdout+stderr) to a file if I don't know which shell (bash, csh, dash) is interpreting my command?
My C code running on Linux/FreeBSD/OSX needs to call an external program via the system() function, which will use /bin/sh to interpret the supplied command line. I would like to capture the messages printed by that external program to stderr and save them to a file. The problem is that on different systems /bin/sh points to different shells that have different syntax for redirecting the stderr stream to a file.
The closest thing I found is that bash actually understands the csh-style syntax for redirecting stderr+stdout to a file:
some_program >& output.txt
but dash, which is the default shell on Ubuntu (i.e. very common), does not understand this syntax.
Is there a syntax for stderr redirection that would be correctly interpreted by all common shells? Alternatively, is there a way to tell system() (or some other similar C function?) to use /usr/bin/env bash instead of /bin/sh to interpret the supplied command line?
You have a mistaken assumption, that /bin/sh can be an "alternate" shell like csh that's incompatible with the standard shell syntax. If you had a system setup like that, it would be unusably broken; no shell scripts would work. Pretty much all modern systems attempt to conform, at least superficially, to the POSIX standard, where the sh command processes the Shell Command Language specified in POSIX, which is roughly equivalent to the historical Bourne shell and which bash, dash, ash, etc. (shells which are commonly installed as /bin/sh) are all 99.9% compatible with.
You can completely ignore csh and similar. They're never installed as sh, and only folks who actually want to use them, or who get stuck using them as their interactive shell because some evil sysadmin setup the login shell defaults that way, ever have to care about them.
On any POSIX-like system, you can use
system("some_program > output.txt 2>&1");
This is because POSIX system is equivalent to calling sh, and POSIX sh supports this kind of redirection. This works independently of whether or not a user opening a terminal on the system will see a Csh prompt.
How do I redirect stderr (or stdout+stderr) to a file if I don't know which shell (bash, csh, dash) is interpreting my command?
You don't. Bourne-family shells and csh-family shells have different, incompatible syntax for redirecting stderr. In fact, csh and tcsh do not have a syntax to redirect only stderr at all -- they can redirect it only together with stdout.
If you really could be in any shell at all, then you're pretty much hosed with respect to doing much of anything. One could imagine an obscure, esoteric shell with completely incompatible syntax. For that matter, even an unusual configuration of a standard shell could trip you up -- for example if the IFS variable is set to an unusual value in a Bourne-family shell, then you'll have trouble executing any commands that don't take that into account.
If you can count on executing at least simple commands, then you could execute a known shell within the unknown one to process your command, but that oughtn't to be necessary for the case that seems to interest you.
Alternatively, is there a way to tell system() (or some other similar
C function?) to use /usr/bin/env bash instead of /bin/sh to interpret
the supplied command line?
Not on a POSIX-conforming system. POSIX specifies explicitly that the system() function executes the command by use of /bin/sh -c [the_command]. But this shouldn't be something to worry about, as /bin/sh should be a conforming POSIX shell, or at least pretty close to one. Definitely it should be a Bourne-family shell, which both bash and dash are, but tcsh most definitely is not.
The way to redirect the standard error stream in a POSIX shell is to use the 2> redirection operator (which is a special case of a more general redirection feature applicable to any file descriptor). Whatever shell /bin/sh actually is should recognize that syntax, and in particular bash and dash both do:
some_program 2> output.txt
I think, there is another possibility worth mentioning: You could open the file you want to redirect on stderr in your c-code prior to calling system(). You can dup() the original stderr first, and then restore it again.
fflush(stderr); // Flush pending output
int saved_stderr = dup(fileno(stderr));
int fd = open("output.txt", O_RDWR|O_CREAT|O_TRUNC, 0600);
dup2(fd, fileno(stderr));
close(fd);
system("some_program");
dup2(saved_stderr, fileno(stderr));
close(saved_stderr);
This should perform the output redirection as you need it.
If you don't know the shell.... of course you don't know how to redirect from it, despite of the fact that you can see what value the $SHELL has, and act in consequence:
char *shell = getenv("SHELL");
if (*shell) { /* no SHELL variable defined */
/* ... */
} else if (!strcmp(shell, "/bin/sh")) { /* bourne shell */
/* ... */
} /* ... more shells */
Despite of what you say in your question, it is quite unusual to rename /bin/sh to use another shell, as shell scripts use syntax that depends on that. The only case I know is with bash(1), and I have seen this only in Linux (and remarkably, last versions of solaris), but the syntax of bash(1) is a superset of the syntax of sh(1), making it possible to run shell scripts made for sh(1) with it. Renaming /bin/sh to perl for example, would make your system probably completely unusable, as many system tools depend of /bin/sh to be a bourne compatible shell.
By the way, the system(3) library function always calls sh(1) as the command interpreter, so there should be no problem to use it, but there's no solution to capture the output and process it by the parent process (indeed, the parent process is the sh(1) that system(3) fork(2)s)
Another thing you can do is to popen(3) a process. This call gives you a FILE pointer to a pipe of a process. You popen its input in case you popen(3) it for writing, and you popen its output if you want or read its output. Look at the manual for details, as I don't know now if it redirects only its standard output or it also redirects the standard error (I think only redirects standard output, for reasons discussed below, and only if you popen(3) it with a "r" flag).
FILE *f_in = popen("ps aux", "r");
/* read standard output of 'ps aux' command. */
pclose(f_in); /* closes the descriptor and waits for the child to finish */
Another thing you can do is to redirect yourself after fork(2)ing the child, and before the exec(2) call (this way you can decide if you want only stdout or if you want also stderr redirected back to you):
int fd[2];
int res = pipe(fd);
if (res < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
if ((res = fork()) < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (res == 0) { /* child process */
dup2(fd[1], 1); /* redirect pipe to stdout */
dup2(fd[1], 2); /* redirect pipe also to stderr */
close(fd[1]); close(fd[0]); /* we don't need these */
execvp(program, argv);
perror("execvp");
exit(EXIT_FAILURE);
} else { /* parent process */
close(fd[1]); /* we are not going to write in the pipe */
FILE *f_in = fdopen(fd[0]);
/* read standard output and standard error from program from f_in FILE descriptor */
fclose(f_in);
wait(NULL); /* wait for child to finish */
}
You can see a complete example of this (not reading standard error, but it is easy to add --- you have only to add the second dup2() call from above) here. The program executes repeatedly a command you pass to it on the command line. It needs to get access to the output of the subprocess to count the lines, as between invocations, the program goes up as many lines as the program output, to make the next invocation to overlap the output of the last invocation. You can try it and play, making modifications as you like.
NOTE
In your sample redirection, when you use >&, you need to add a number after the ampersand, to indicate which descriptor you are dup()ing. As the number before the > is optional, the one after the & is mandatory. So, if you have not used it, prepare to receive an error (which probably you don't see if you are redirecting stderr) The idea of having two separate output descriptors is to allow you to redirect stdout and at the same time, conserve a channel where to put error messages.

C program to run xyz.out file in another terminal

I can open a new terminal by this code,
char *argv[]={"gnome-terminal"," -x ","/home/try/",NULL};
if(execvp(argv[0], argv)==-1){
printf("Error in receiver\n");
exit(EXIT_FAILURE);
}
This will open a terminal with path /home/try
I can open another program through
system("./xyz");
I have a program say, pqr.c, and i want to open a already existing program xyz.c through it, but i want the output of pqr and xyz to be displayed on two different terminal.
How to do this?
You can make use of your terminal program's own capabilities. Most (if not all) Unix terminal emulators can take an argument which tells them what program to run, instead of the user's shell.
gnome-terminal is no different here and also it uses the most common parameter for the task: -e.
So your code can look like:
char *argv[]={
"gnome-terminal",
"-x", "/home/try/", // <-- note: no space before or after "-x"
"-e", "/path/to/your/program",
NULL};
if(execvp(argv[0], argv)==-1){
printf("Error in receiver\n");
exit(EXIT_FAILURE);
}
Some things to note:
The terminal will close as soon as its inner program (/path/to/your/program) finishes execution. To prevent that, you can make the program wait for some input before termnating.
As noted in the code snippet, there should be no additional spaces around program arguments. If gnome-terminal works fine with them, this only means that it strips those spaces while parsing arguments.

Dealing with pipes in C

I am trying to implement a shell in C language on Linux.This project that I am working on, asks to create a shell in C, starting from creating a very basic one (myShell), which goes deeper step by step.First I had to create a shell with simple commands like
ls,pwd,echo,date,time
(shell1).
After that, my Shell had to be improved so it could sort things from files (txt's for example) (shell2) and as it goes on, I had to do more by developing it, and make it execute commands like
sort -r,sort -u.(shell3).
Until the 3rd shell, I was working with redirections and everything was going well.
Now for the 4th shell, I am supposed to make it run commands with pipes, e.g. ls -l /home/ | sort > out.txt. I have managed to make the command work, the out.txt file gets created succesfully and is sorted accordingly. There is a while() on my code, so that after every command that I give to the shell it asks for the next one etc. etc.. But when the above example command is given and the pipes are used, the program stops. The terminal doesn't show "myShell4>" but Desktop$ and it basically exits the shell. Giving it simple commands like "ls -l" ,that don't use the pipes, works perfectly, so from that I realise that the problem is in the pipes and that they stop my program from looping.
The part where this happens in my code:
//CHILD
dup2(pipefd[1],1);
close(pipefd[0]);
execute(cmd,path,argm);
//PARENT
dup2(pipefd[0],0);
close(pipefd[1]);
execlp(cmd2,cmd2,NULL);
Any thoughts? Thanks in advance!
The parent is the shell, right? Don't exec there; create children for both ends of the pipe and wait for them in the parent. If you do, the shell will be replaced and no longer run after the command ends.
Below is some pseudo-code for a pipe between two commands:
int pipefd[2];
pipe (pipefd);
// child for first command
if (fork () == 0) {
// setup in redirection if any
...
// setup out redirection
close (pipefd[0]);
dup2 (pipefd[1], STDOUT_FILENO);
...
exec (cmd1, ...);
exit (1);
}
// child for second command
if (fork () == 0) {
// setup in redirection
close (pipefd[1]);
dup2 (pipefd[0], STDIN_FILENO);
// setup out redirection if any
dup2 (output_file_fd, STDOUT_FILENO);
exec (cmd2, ...);
exit (1);
}
close (pipefd[0]);
close (pipefd[1]);
// parent waits and then restarts the loop
wait (NULL);
wait (NULL);
Things get more complicated for a list of more than two commands connected by pipes.

About bash, process scheduling and printf()

The main code is follow:
int main(){
pid_t pid=fork();
if(pid==0){
printf("a\n");
exit(0);
}
else
printf("b\n");
return 0;
}
The output is follow:
b
aimager#cong-Ubuntu:/mnt/LinuxDatum/WorkSpace/Ubuntu$ a
The question is: Why "aimager#cong-Ubuntu:/mnt/LinuxDatum/WorkSpace/Ubuntu$" front output in a
You've executed process in foreground, so shell waits for its return before asking for next command. This process launches one more child process, which, for shell, is background. When initial process finishes, shell, unknowing anything about any subprocesses it never launched, asks you for next command with command prompt (in your case - it's username#hostname:/current/working/directory$), but after this background process decides to print some data. Prompt is already there, noone is going to remove it, so this data just appended here.
It only affects how you see things. Shell didn't got this data so it isn't added to command string, it just displayed that way. You could press return to force re-prompt if you like to see clear line.
You may see quite the same results with
$ (echo foo; echo bar) &
(& is command to shell to launch process in background - ask for next command without waiting for previous command to complete)

The exec family

I have a project the requires the use of the exec family. My project consist of making an interactive shell. The shell will implement a few basic commands like cd, ls, echo, etc. I have been researching the use of exec, but have not found a useful site. Any suggested links would help.
int ret;
ret = execl ("/bin/ls", "ls", "-1", (char *)0);
How would i get the output of this operation to show on the screen?
doing
int fd = 1;
dup(fd);
close(fd);
gets the output to the screen.
The code you wrote works for me in a simple test program that does nothing else. Remember, when you call execl, the process retains all of the old file handles. So whatever stdout was when you call execl, it will be the same when the new binary is loaded. If you just want the output to go to the terminal, just make sure stdout goes to the terminal.
If you want to do I/O with another program, popen is good for this (as mgb mentioned). It will fork a new process, set up plumbing for you, call some variant of exec, and return a file handle you can use for communication.

Resources