Program linux command with pipe in C - c

I´m trying to execute the next linux command
cat file_a file_b file_c | wc –l > result.txt
in a C program, but I´m not able to do it properly. I have very low level of C programming, and I would like to see how to make that command works in a C program.
This is the code I developed without success:
void main() {
execlp("/bin/sh", "/bin/sh", "-c", "cat file1 file2 fileN | wc –l > lines.txt", 0);
}

I follow your example.
Its results:
implicit declaration of function 'execlp' is invalid in C99
And some other warning errors.
But I think it would be better if you use the system() C-function, here is what I've done:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
system("cat filea fileb filec | wc -l > result.txt");
return EXIT_SUCCESS;
}
After compilation that works!
If you want to run just a command without needing to read the resulting output, you could use the C-function system(), but if you want to run it getting its results, you should use popen().
system()
popen()

Related

Why doesn't the execve command in C on macOS allow the 'which' command to work?

Why does the execve command in C on macOS not allow the 'which' command to work? It works on non-Mac devices.
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd;
char cmd[] = "/bin/cat";
char cmd1[] = "/usr/bin/which";
char *s[]={"which","ls",NULL};
if (execve(cmd1, s, NULL) == -1)
perror("oops ur wrong!!");
}
Expected output
 clang-7 -pthread -lm -o main main.c
 ./main
/bin/ls

but on a Mac, it returns nothing.
macOS
The code works. It doesn't work well, but it does work.
Given the null PATH in the environment (because you've used execve() and provided NULL as the environment), /usr/bin/which can't find ls — it has nowhere to look for it because PATH is not set.
On my machine (a MacBook Pro running macOS Big Sur 11.7.1 — it's a work machine and the company IT is behind the times), /usr/bin/which is a universal binary with two architectures. If I run /usr/bin/which ozymandias on the command line, there is no output (I don't have a command ozymandias anywhere), but the exit status is 1 (failure). That's an odd implementation — not reporting an error — but it works within its limits.
You can see this effect with:
$ (unset PATH; /usr/bin/which ls)
$ echo $?
1
$
If you use execv() instead of execve() and remove the , NULL from the argument list, the output is /bin/ls and the exit status is 0.
Linux
Just for comparison, on a RHEL 7.4 machine, I get different results:
$ which -a which
which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
/usr/bin/alias
/usr/bin/which
/usr/bin/which
$ file /usr/bin/which
/usr/bin/which: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=317ba624d2914607bf9246993446803a977fbc18, stripped
$ /usr/bin/which which
/usr/bin/which
$ (unset PATH; /usr/bin/which which)
/usr/bin/which: no which in ((null))
$ /usr/bin/which ozymandias
/usr/bin/which: no ozymandias in (/work2/jleffler/bin:/u/jleffler/bin:/usr/perl/v5.34.0/bin:/usr/gcc/v12.2.0/bin:/usr/local/bin:/usr/bin:/usr/sbin)
$ /usr/bin/which --help
Usage: /usr/bin/which [options] [--] COMMAND [...]
Write the full path of COMMAND(s) to standard output.
--version, -[vV] Print version and exit successfully.
--help, Print this help and exit successfully.
--skip-dot Skip directories in PATH that start with a dot.
--skip-tilde Skip directories in PATH that start with a tilde.
--show-dot Don't expand a dot to current directory in output.
--show-tilde Output a tilde for HOME directory for non-root.
--tty-only Stop processing options on the right if not on tty.
--all, -a Print all matches in PATH, not just the first
--read-alias, -i Read list of aliases from stdin.
--skip-alias Ignore option --read-alias; don't read stdin.
--read-functions Read shell functions from stdin.
--skip-functions Ignore option --read-functions; don't read stdin.
Recommended use is to write the output of (alias; declare -f) to standard
input, so that which can show aliases and shell functions. See which(1) for
examples.
If the options --read-alias and/or --read-functions are specified then the
output can be a full alias or function definition, optionally followed by
the full path of each command used inside of those.
Report bugs to <which-bugs#gnu.org>.
$
PATH sanitized — radically shortened.
The which command reports an error when it can't find the command. It is a standalone executable on this Linux machine, and the which alias feeds it the aliases so it can report on them. The -a option reports on all the things that could be known as which (the second which in which -a which).
I found that adding the envp(path argument in main) to the arguments made it work
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(int argv, char *argc[],char *envp[])
{
int fd;
char cmd1[] = "/usr/bin/which";
char *s[] = {"which", "ls", NULL};
if (execve(cmd1, s, envp) == -1)
perror("oops ur wrong!!");
}
thanks anyways

Running two commands using C and Linux environment variables

I'm trying to run two commands using a C program and a Linux environment variable:
#Program name is execute
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char cmd[256] = "/home/username/hello.sh $USER";
execl("/bin/bash", "bash", "-p", "-c", cmd, NULL);
return 0;
}
However, when running the program with the $USER environment variable set to a second command, the second command will not run.
env USER=";cat /home/username/hello.txt" ./execute
Hello from shell script
Hard coding the second command into the C program works:
char cmd[256] = "/home/username/hello.sh ;cat /home/username/hello.txt";
./execute
Hello from shell script
Hello from text file
I want my C program to return:
env USER=";cat /home/username/hello.txt" ./execute
Hello from shell script
Hello from text file
How can I get it to work, without changing the C program?
The only processing that's done on the result of expanding variables is word splitting and globbing. It doesn't process characters like ; to separate commands, > for output redirection, $ for further variable expansions, etc.
If you want to force a full parse of the command, you have to use eval.
char cmd[256] = "eval /home/username/hello.sh $USER";
execl("/bin/bash", "bash", "-p", "-c", cmd, (char*)NULL);

Execute a concatenation command with execvp

How can I execute the following command using execvp, if it's possible:
"ls | tee ~/outputfile.txt"
I've tried to run the following code but got this message: execvp() not expected: No such file or directory
I'm not sure for the cause of this issue,
I can't execute this command because this is concatenation command ?
#include <unistd.h> // execvp()
#include <stdio.h> // perror()
#include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE
int main(void) {
char *const cmd[] = {"ls | tee ~/outputfile.txt", NULL};
execvp(cmd[0], cmd);
perror("Return from execvp() not expected");
exit(EXIT_FAILURE);
}
In the bottom line, want to write the output of the command 'ls' to a file in my code.
Thank you in advance!
You can't use execvp (or any exec* function family) like that.
First of all, the first argument must be the path to the executable file.
I doubt you have an 'ls | tee ~/outputfile.txt' executable somewhere on your computer.
You have 'ls' or 'tee' probably, but not 'ls | tee ~/outputfile.txt'.
Secondly : exec* function family can't do nativly piping (the '|' part) : you have to do it yourself.
An example is the following :
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *const cmd[] = {"ls", "/home", NULL};
execvp(cmd[0], cmd);
perror("Return from execvp() not expected");
exit(EXIT_FAILURE);
}
That will do a ls in "/home".
It's up to you to pipe it in another execve : this can greatly help you.
If you just want to execute your command line wthout any regard for security, you can use "system"

Bash reopen tty on simple program

#include <stdio.h>
#include <stdlib.h>
int main()
{
char buf[512];
fgets(buf, 512, stdin);
system("/bin/sh");
}
Compile with cc main.c
I would like a one-line command that makes this program run ls without it waiting for user input.
# This does not work - it prints nothing
(echo ; echo ls) | ./a.out
# This does work if you type ls manually
(echo ; cat) | ./a.out
I'm wondering:
Why doesn't the first example work?
What command would make the program run ls, without changing the source?
My question is shell and OS-agnostic but I would like it to work at least on bash 4.
Edit:
While testing out the answers, I found out that this works.
(python -c "print ''" ; echo ls) | ./a.out
Using strace:
$ (python -c "print ''" ; echo ls) | strace ./a.out
...
read(0, "\n", 4096)
...
This also works:
(echo ; sleep 0.1; echo ls) | ./a.out
It seems like the buffering is ignored. Is this due to the race condition?
strace shows what's going on:
$ ( echo; echo ls; ) | strace ./foo
[...]
read(0, "\nls\n", 4096) = 4
[...]
clone(child_stack=NULL, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffdefc88b9c) = 9680
In other words, your program reads a whole 4096 byte buffer that includes both lines before it runs your shell. It's fine interactively, because by the time the read happens, there's only one line in the pipe buffer, so over-reading is not possible.
You instead need to stop reading after the first \n, and the only way to do that is to read byte by byte until you hit it. I don't know libc well enough to know if this kind of functionality is supported, but here it is with read directly:
#include <unistd.h>
#include <stdlib.h>
int main()
{
char buf[1];
while((read(0, buf, 1)) == 1 && buf[0] != '\n');
system("/bin/sh");
}

How to parse a pipe or redirection operator in unix?

I am currently working on a cat program. The problem I am having is that whenever I execute the unix command:
./catpr file1 file2 file3 | grep string
I don't know how to mark the end of the parsing by pointing out there exists a pipe operator. I am currently developing this cat program in C by using system calls, so I use argv to keep track of the arguments; however, whenever I look at the content in the array that corresponds to '|', something really weird shows up.
So what you basically need is you need to search a pattern in three files.
Since you have specified that you are using system calls to perform what you can do is give the string as input argument to program. Just do the following for compiling
./catpr file1 file2 file3 string
Just inside the program give the system call with grep. I am providing the complete program for you. I hope this is what you need.
#include <stdio.h>
#include <string.h>
int main(int a,char *argv[])
{
char com[256]="";
strcat(com,"cat ");
strcat(com,argv[1]);
strcat(com," ");
strcat(com,argv[2]);
strcat(com," ");
strcat(com,argv[3]);
strcat(com," | grep ");
strcat(com,argv[4]); //this is your search pattern
system(com);
}

Resources