Commands: Simple text-based terminal tools

There was a time, long before the ubiquitous windows management on our desktops and desktops, when people used commands to get things done from the computer. These commands were entered on the terminal. There were no beautiful interfaces involved like the menus or the buttons. To see the contents of the directory, they go to the terminal and enter the words ‘ls’ and they see a list of names of files and directories displayed on their terminal with no fancy figures. Similar for every action we now do on the computer, there was an associated command.

Command means to order somebody to do somethings. In the world of computers, there are a fixed number of commands that we give to the computer and the computer can understand these commands. Though there are many research works to make computer understand a large number of commands. But as of 2012, the computers can only understand a limited set of commands.

A command is a software. But normally the word ‘command’ corresponds to the software that run on the terminal. These commands take as input textual information from the user (like the name of a file or an image) and generates textual information as output.

Some commonly used commands in Linux
• ls (to list the contents of a directory)
• cd (change directory)
• pwd (prints current working directory)
• grep (to search for a pattern in a file)

Programming with FIFO: mkfifo(), mknod()

FIFOs can be created from the shell. But they can also be created using programs. There are two ways by which FIFOs can be created from the program

Creating a FIFO

1. mknod

Syntax

int mknod(const char *pathname, mode_t mode, dev_t dev);

where pathname corresponds to the fifo name, mode corresponds to the file permissions. Since mknod can be used to create a regular file, block or charcter special files and fifo. We have to specify the file type. Corresponding to FIFO: the file type is S_IFIFO.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[]){
     int result;
     if (argc != 2) {
             fprintf(stderr, "Usage: ./a.out fifoname\n");
             exit (1);
     }

     result = mknod (argv[1], S_IRUSR| S_IWUSR|S_IFIFO, 0);
     if (result < 0) {
          perror ("mknod");
          exit (2);
       }
  }

Here we wish to give both read and write permissions to the user. So we specified S_IRUSR | S_IWUSR| S_IFIFO in the mode. If the file type is S_IFCHR or S_IFBLK then dev is checked; otherwise it is ignored. So we passed 0 as the argument.

2. mkfifo

Syntax

int mkfifo(const char *pathname, mode_t mode);

where pathname correspnds to fifo name and mode corresponds to file mode.

 # include <stdio.h>
 # include <stdlib.h>
 # include <sys/types.h>
 # include <sys/stat.h>

int main(int argc, char *argv[]){
     int result;

     if (argc != 2) {
          fprintf(stderr, "Usage: ./a.out fifoname\n");
          exit (1);
     }

    result = mkfifo (argv[1], S_IRUSR| S_IWUSR);
     if (result < 0) {
          perror ("mkfifo");
          exit (2);
      }
  }

Here we need not explicitly specify the file type S_IFIFO. You can (if you wish) specify S_IFIFO in the mode.

 

Using Pipes in Linux Processes

Pipes can be used in threads and processes. The program below demonstrates how pipes can be used in processes. A new process can be created using the system call fork(). It returns two differnt values to the child and parent. The value 0 is returned to the child (new) process and the PID (Process ID) of the child is returned to the parent process. This is used to distinguish between the two processes. In the program given below, the child process waits for the user input and once an input is entered, it writes into the pipe. And the parent process reads from the pipe.

A sample program to demonstrate how pipes are used in Linux Processes

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define MSGLEN  64

int main(){
    int     fd[2];
    pid_t pid;
    int     result;

    //Creating a pipe
    result = pipe (fd);
    if (result < 0) {
        //failure in creating a pipe
        perror("pipe");
        exit (1);
    }

    //Creating a child process
    pid = fork();
    if (pid < 0) {
         //failure in creating a child
         perror ("fork");
         exit(2);
    }

    if (pid == 0) {
        //Child process
         char message[MSGLEN];

          while(1) {
                    //Clearing the message
                    memset (message, 0, sizeof(message));
                    printf ("Enter a message: ");
                    scanf ("%s",message);

                    //Writing message to the pipe
                    write(fd[1], message, strlen(message));
            }
            exit (0);
    }
    else {
        //Parent Process
         char message[MSGLEN];

         while (1) {
                    //Clearing the message buffer
                    memset (message, 0, sizeof(message));

                    //Reading message from the pipe
                   read (fd[0], message, sizeof(message));
                    printf("Message entered %s\n",message);
            }

            exit(0);
     }
}

Note here that the pipe() system call was called before the system call fork(). The buffer allocated to the pipe is accessible to both the processes.

 

FIFO – Named pipes: mkfifo, mknod

Pipes are commonly used for interprocess communication. But the major disadvantage of pipes is that they can be used only by one process (there are readers and writers within the same process) or the processes which share the same file descriptor table (normally the processes and the child processes or threads created by them). Thus pipes have this big limitation: they cannot pass information between unrelated processes. This is because they do not share the same file descriptor table. But if names are given to the pipes, then one would be able to read or write data to them just like a normal file. The processes need not even share anything with each otherFIFO (First In First Out) are also called named pipes. The main features of FIFO are
1. It implements FIFO feature of the pipes
2. They can be opened just like normal files using their names
3. Data can be read from or written to the fifo

Working with FIFO in a Shell

Creating a FIFO

mkfifo

creates fifo- the named pipes

Syntax

mkfifo [options] fifo_name

Example

$ mkfifo fifo

There is one more way by which we can FIFO using mknod. mknod is used to create block or character special files.

$ mknod [OPTION]... NAME TYPE

To create a FIFO fifo1

$ mknod fifo1 p

where p coressponds to file type : pipe (remember FIFO is a named pipe).

Reading/ Writing data from/to a FIFO
Let’s open two terminals
In the first terminal

$ cat > fifo

we are experimenting with the FIFOThis is second line. After opening the fifo in the second terminal for readingusing cat, you will notice the above two lines displayed there.
Now open the second terminal and go to the directory containing the FIFO ‘fifo’

$ cat fifo

we are experimenting with the FIFOThis is second line. After opening the fifo in the second terminal for reading
Now keep on writing to the first terminal. You will notice that every time you press enter, the coressponding line appears in the second terminal.

Pressing CTRL+D in the first terminal terminates writing to the fifo. This also terminates the second process because reading from the fifo now generates a “BROKEN PIPE” signal. The default action for this is to terminate the process.

Let us now see the details of the file ‘fifo’

$ ls -l fifo
prw-r--r-- 1 user user 0 Feb 14 10:05 fifo

The p in the beginning denotes that it is a pipe.

Let’s see more details about the pipe using stat

$ stat fifo
File: `fifo'Size: 0               Blocks: 0          IO Block: 4096   fifo
Device: fd00h/64768d    Inode: 1145493     Links: 1
Access: (0644/prw-r--r--)  Uid: (    0/    user)   Gid: (    0/    user)
Access: 2008-02-14 10:05:49.000000000 +0530
Modify: 2008-02-14 10:05:49.000000000 +0530
Change: 2008-02-14 10:05:49.000000000 +0530

If you notice carefully, FIFOs just like a normal file possess all the details like inode number, the number of links to it, the access, modification times, size and the access permissions.

As in the case of pipes, there can be multiple readers and writers to a pipe. Try opening multiple terminals to read from and write to a pipe.

 

Operations on Pipe (Advanced Pipe Usage)

we discussed how we can work with pipe() system call, where it is used(Linux shell), how to use pipes in threads(processes).

Here we can discuss on the topic ‘Number of readers and writers accessing a pipe’
A pipe can have more than one reader and more than one writer. Only one thing that must be kept in mind is that writer writes into fd[1] and reader reads from fd[0], where fd is the filedescriptor passed for the pipe() call.

int pipe(int fd[2]);

The program below demonstrates this with two writers and one reader. The writer Writer_ABC writes the capital Alphabet and the Writer_abc writes the small alphabet into the pipe. The reader reads in the order of the letters written by the two writers

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>

int fd[2];//File descriptor for creating a pipe

//This function continously reads fd[0] for any input data byte
//If available, prints

void *reader()
{
    while(1){
       char    ch;
       int     result;

       result = read (fd[0],&ch,1);
       if (result != 1) {
            perror("read");
            exit(3);
    }    printf ("Reader: %c\n", ch);  }
}

//This function continously writes Capital Alphabet into fd[1]
//Waits if no more space is available

void *writer_ABC()
{
     int     result;
     char    ch='A';

     while(1){
           result = write (fd[1], &ch,1);
           if (result != 1){
               perror ("write");
               exit (2);
           }

           printf ("Writer_ABC: %c\n", ch);
           if(ch == 'Z')
              ch = 'A'-1;

           ch++;
      }
}

//This function continously writes small Alphabet into fd[1]
//Waits if no more space is available

void *writer_abc()
{
  int     result;  char    ch='a';

  while(1){
      result = write (fd[1], &ch,1);
      if (result != 1){
            perror ("write");
            exit (2);
      }

      printf ("Writer_abc: %c\n", ch);
      if(ch == 'z')
            ch = 'a'-1;

     ch++;
  }
}

int main()
{
   pthread_t       tid1,tid2,tid3;
   int             result;

   result = pipe (fd);
   if (result < 0){
       perror("pipe ");
       exit(1);
   }

 pthread_create(&tid1,NULL,reader,NULL);
 pthread_create(&tid2,NULL,writer_ABC,NULL);
 pthread_create(&tid3,NULL,writer_abc,NULL);

 pthread_join(tid1,NULL);
 pthread_join(tid2,NULL);
 pthread_join(tid3,NULL);
}

Output

Writer_abc: o
Writer_abc: p
Writer_abc: q
Writer_abc: r
Writer_abc: s
Writer_abc: t
Writer_abc: u
Reader: A
Reader: B
Reader: C
Reader: D
Reader: E
Reader: F
Reader: G
Reader: H
Reader: I
Reader: J
Reader: K
Reader: L
Reader: M
Reader: N
...

Piping in threads

Pipes are quite useful in threads. Imagine a scenario, where one thread is continously monitoring for new user input and another thread performs some processing on this data input. This can be achieved with the help of shared memory. But in case of shared memory, one has to think about data synchronization. Pipes can be made use of here. One thread waits and reads the user input and writes it into the pipe. The second thread can read from the pipe and perform processing.

Let’s take a simple example. The program below shows the READER-WRITER threads. The WRITER thread continously writes the alphabets into the pipe. and the READER thread reads it. See clearly that fd[2] is declared globally so that both threads can access it. Also note that the pipe() system call was called before threads creation.

To compile a program with threads, we must specify -lpthread to link with the POSIX threads library
$ gcc program -lpthread
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

int fd[2];//File descriptor for creating a pipe

//This function continously reads fd[0] for any input data byte
//If available, prints

void *reader()
{
   while(1){
      char    ch;
      int     result;

      result = read (fd[0],&ch,1);
      if (result != 1) {
        perror("read");
        exit(3);
      }

      printf ("Reader: %c\n", ch);   }
}

//This function continously writes Alphabet into fd[1]
//Waits if no more space is available

void *writer()
{
   int     result;
   char    ch='A';

   while(1){
       result = write (fd[1], &ch,1);
       if (result != 1){
           perror ("write");
           exit (2);
       }

       printf ("Writer: %c\n", ch);
       if(ch == 'Z')
         ch = 'A'-1;

       ch++;
   }
}

int main()
{
   pthread_t       tid1,tid2;
   int             result;

   result = pipe (fd);
   if (result < 0){
       perror("pipe ");
       exit(1);
   }

   pthread_create(&tid1,NULL,reader,NULL);
   pthread_create(&tid2,NULL,writer,NULL);

   pthread_join(tid1,NULL);
   pthread_join(tid2,NULL);
}

Output

...
Reader: Y
Reader: Z
Reader: A
Reader: B
Reader: C
Reader: D
Reader: E
Reader: F
Reader: G
Reader: H
Reader: I
Reader: J
Reader: K
Reader: L
Reader: M
Reader: N
Reader: O
Reader: P
Reader: Q
Reader: R
Reader: S
Reader: T
Reader: U
Reader: V
Reader: W
Reader: X
Reader: Y
Reader: Z
Reader: A
Reader: B
Reader: C
...

After doing this, let’s check what’s the capacity of a pipe. Anyway pipe also utilizes memory, so there must be an upper limit to the capacity of a pipe. A writer thread keeps on writing to the pipe hoping that the READER thread is reading it. Let’s put some delay in READER thread (sleep() call). so that write will at some point of time stop writing the data to the pipe.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>

int fd[2];//File descriptor for creating a pipe

//This function continously reads fd[0] for any input data byte
//If available, prints

void *reader()
{

 int     count = 0;
 sleep (25);
  //Delay in starting the reading from the pipe

  while(1){
      char    ch;
      int     result;

      result = read (fd[0],&ch,1);
      if (result != 1) {
          perror("read");
          exit(3);
      } printf ("Reader: %d %c\n",++count,ch);
  }
}

//This function continously writes Alphabet into fd[1]

void *writer()
{
    int     result;
    char    ch='A'; int     count = 0;

    while(1){

       result = write (fd[1], &ch,1);
       if (result != 1){
           perror ("write");
           exit (2);
       }

       printf ("Writer: %d %c\n", ++count, ch);

       if(ch == 'Z')
          ch = 'A'-1;

        ch++;
   }
}

int main()
{
   pthread_t       tid1,tid2;
   int             result;

   result = pipe (fd);

   if (result < 0){
        perror("pipe ");
        exit(1);
   }

   pthread_create(&tid1,NULL,reader,NULL);
   pthread_create(&tid2,NULL,writer,NULL);

   pthread_join(tid1,NULL);
   pthread_join(tid2,NULL);
}

Output
...
Writer: 65524 D
Writer: 65525 E
Writer: 65526 F
Writer: 65527 G
Writer: 65528 H
Writer: 65529 I
Writer: 65530 J
Writer: 65531 K
Writer: 65532 L
Writer: 65533 M
Writer: 65534 N
Writer: 65535 O
Writer: 65536 P

On executing the above program, the WRITER thread stopped at the count = 65536 waiting for READER thread to read the data already written. After the specified delay (here 25 seconds), the READER thread resumes its operation and starts reading. Then both WRITER and READER thread works normally.So pipe() is useful for implementing QUEUE mechanism of sending the data from one thread(process) to another keeping in mind that the pipe also has a capacity limit. So if the reader is blocked or if there is no reader, the writer will block at some point of time if it reached the capacity of the pipe.

 

Using Pipes in Linux Programming

Pipes are one of the most commonly used mechanisms for IPC (inter process communication). IPC is the mechanism by which two or more processes communicate with each other. The commonly used IPC techniques includes shared memory, message queues, pipes and FIFOs (and sometimes even files). Pipes are helpful in the unidirectional form of communication between processes.The pipe() creates a pipe.Syntax
int pipe(fd[2])pipe() creates a pair of file descriptors, pointing to a pipe inode, and places them in the array pointed to by fd. fd[0] is for reading, fd[1] is for writing.Let’s see a simple program how to use piping.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MSG_LEN 64

int main(){

      int     result;
      int     fd[2];
      char    *message="Linux World!!!";
      char    recvd_msg[MSG_LEN];

      result = pipe (fd); //Creating a pipe fd[0] is for reading and fd[1] is for writing

      if (result < 0) {
          perror("pipe ");
          exit(1);
      }

      //writing the message into the pipe

      result=write(fd[1],message,strlen(message));
      if (result < 0) {
          perror("write");
          exit(2);
      }

      //Reading the message from the pipe

      result=read (fd[0],recvd_msg,MSG_LEN);

      if (result < 0) {
          perror("read");
          exit(3);
      }

      printf("%s\n",recvd_msg);
      return 0;
}

Output

Linux World!!!

As seen above , the pipe() system call creates a pipe. Now normal system calls like read() or write() can be used for reading and writing data to the pipe. As you have noticed the fd[0] can only be used for reading from the pipe and does not permit writing. Similarly fd[1] can only be used to perform writing to the pipe.

Seeing the above program, one would feel

what is the neccessity of piping?.

You are writing into the pipe and reading from it. That one can do even from a file.

Now let’s see another scenario. If we add more writes in the above program before reading anything and then start reading, you will find that the first read call will fetch you the first written message and so on. So

pipe() is helpful in Implementing a QUEUE strategy(First in First out)

FIFO: the message which was written first will be available for the first read, then the message which came second, for the second read.

Note: The first read itself can get all of the messages written by different write, if it specifies a large size in the size field (a size which can span the size of one or more writes ) as demonstrated here

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MSG_LEN 64

int main(){
   int     result;
   int     fd[2];
   char    message[MSG_LEN];
   char    recvd_msg[MSG_LEN];

   result = pipe (fd); //Creating a pipe//fd[0] is for reading and fd[1] is for writing

   if (result < 0) {
     perror("pipe ");
     exit(1);
   }

   strncpy(message,"Linux World!! ",MSG_LEN);

   result=write(fd[1],message,strlen(message));
   if (result < 0) {
      perror("write");
      exit(2);
   }

   strncpy(message,"Understanding ",MSG_LEN);

   result=write(fd[1],message,strlen(message));
   if (result < 0) {
      perror("write");
      exit(2);
  }

  strncpy(message,"Concepts of ",MSG_LEN);

  result=write(fd[1],message,strlen(message));
  if (result < 0) {
      perror("write");
      exit(2);
 }

 strncpy(message,"Piping ", MSG_LEN);
 result=write(fd[1],message,strlen(message));

 if (result < 0) {
     perror("write");
     exit(2);
 }

 result=read (fd[0],recvd_msg,MSG_LEN);
 if (result < 0) {
      perror("read");
      exit(3);
 }

 printf("%s\n",recvd_msg);
 return 0;

}

Output:

Linux World!! Understanding Concepts of Piping

In the above program, The read() call reads all the messages in a single stretch, because of the MSG_LEN(64) which is greater than sum of the size of the messages written in four different writes. The only thing that we must remember that the read() reads in the order of messages written.

Piping in Linux : How to use pipes in shell?

One of the most widely used feature in Linux shell is the pipe (|). Piping in a shell helps in redirecting the output of one command as an input to the other. The main advantage of piping is that it helps in combining simple commands to achieve a particular purpose. In fact even if you know some basic commands in Linux, you can make the best use of your knowledge by using piping. One more advantage is that the total number of commands will be reduced because what can be achieved from a single new command can be achieved by combining two already present commands. In addition to this, the number of options for a particular command can be reduced because a new option is not required if we can achieve the same using piping.Before moving on with examples, let’s see the commands which I am going to use to explain piping1. ls :
list directory contents
To list the files in the directory /bin

$ ls /bin
alsacard
alsaunmute
arch
awk
basename
bash
catchgrp
chmod
chown
cp
cpio
csh
.....

2. wc:
To print the number of newlines, words and bytes in files

$ wc test.c
44   93 1179 test.c

where the number of newlines, words and bytes in test.c is 44, 93 and 1179 respectively

To print the number of newlines, -l option is used

$ wc -l test.c
44 test.c

Now suppose to find the count of newlines we write on the terminal

$ wc -l
After writing
press ctrl + d
this will display the
numberof lines
4

Thus wc can take input both from the terminal and from a file

3. grep :
print lines matching a pattern
To print the lines which contain the pattern ‘main’ in test.c

$ grep main test.c
int main (int argc, char *argv[])

Now if we wish to print the line number in addition to the above information, -n option is used

$ grep -n main test.c
25:int main (int argc, char *argv[])

Just like wc, grep can also take input from the terminal

$ grep -n main
we are writing
some words on the terminal
and using grep to find the
line where we wrote main
4:line where we wrote main(^D)

grep as soon as it finds the pattern ‘main’ displays the corresponding line on the terminal.

4. ps :
report a snapshot of the current processes
To see the processes running in the current shell

$ ps PID TTY TIME CMD 593 pts/2 00:00:00 bash 11270 pts/2 00:00:00 ps

To see the details of all the processes run by all the users

$ ps

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.1   2044   512 ?        Ss   Jan29   0:00 init [5]
root         2  0.0  0.0      0     0 ?        S    Jan29   0:00 [migration/0]
root         3  0.0  0.0      0     0 ?        SN   Jan29   0:00 [ksoftirqd/0]
root         4  0.0  0.0      0     0 ?        S    Jan29   0:00 [watchdog/0]
root         5  0.0  0.0      0     0 ?        S<   Jan29   0:00 [events/0]
root         6  0.0  0.0      0     0 ?        S<   Jan29   0:00 [khelper]
...

Let’s see some examples to demonstrate piping

Example 1
To see the number of files or subdirectories starting with the letter ‘a’ in a directory /bin

$ ls /bin/a*
/bin/alsacard
/bin/alsaunmute
/bin/arch/bin
/awk

It is quite clear that the number of files is four. Here we can manually count them, but suppose there are more entries. Let’s use wc for the purpose, wc -l can count the number of newlines. So we can redirect the output of ls to wc. (Note wc can take input from both the file and the terminal)

$ ls /bin/a* | wc -l4

Thus using piping we have achieved our purpose. The output of ls which has to be displayed on the terminal, has been redirected as an input to wc (which was expecting input from the terminal).

Example 2

We can use more than one pipes. Suppose we wish to find the number of commands containg pattern ‘aw’

$ ls /bin/ | grep aw
awk
gawk
igawk
pgawk

The above only displays the commands containing the pattern ‘aw’. But we need the count. Let’s use wc and one more pipe

$ ls /bin/ | grep aw | wc -l4

Thus we can have more than one pipe to achieve a purpose

Example 3
Suppose we wish to know the details of all the processes run by the user ‘root’
We can use ps to search for the process details and grep to search for the pattern ‘root

root         1  0.0  0.1   2044   512 ?        Ss   Jan29   0:00 init [5]
root         2  0.0  0.0      0     0 ?        S    Jan29   0:00 [migration/0]
root         3  0.0  0.0      0     0 ?        SN   Jan29   0:00 [ksoftirqd/0]
root         4  0.0  0.0      0     0 ?        S    Jan29   0:00 [watchdog/0]
root         5  0.0  0.0      0     0 ?        S<   Jan29   0:00 [events/0]
root         6  0.0  0.0      0     0 ?        S<   Jan29   0:00 [khelper]
root         7  0.0  0.0      0     0 ?        S<   Jan29   0:00 [kthread]
...
abcd     12369  0.0  0.1   3892   656 pts/2    R+   13:55   0:00 grep root

You would have noticed in the last line user ‘abcd’ is displayed, because of the pattern root in ‘grep root’.

Example 4
Suppose we wish to know the details of a process like ‘firefox‘. We can use ps to find that. But instead of manually looking for the particular command in the long displayed list from ps, we can make use of the piping. grep can be used to search for a particular pattern. So the output of ps can be redirected to the grep command which can be used for searching the command.

$ ps -x | grep firefox

 5100 ? S 0:00 /bin/sh /usr/lib/firefox-1.5.0.7/firefox -UILocale en-US 5122 ? S 0:00 /bin/sh /usr/lib/firefox-1.5.0.7/run-mozilla.sh /usr/lib/firefox-1.5.0.7/firefox-bin -UILocale en-US 5127 ? Sl 1:03 /usr/lib/firefox-1.5.0.7/firefox-bin -UILocale en-US 5284 pts1 S+ 0:00 grep firefox

Thus instead of designing a new command for searching a particular running process, we have utilized the concept of piping.

 

The working of Piping in a Linux Shell

We use piping to utilize the output of one command as an input to another. For example , if we wish to count the number of files in a directory(not recursive)

$ls -n| wc -l

The question is how does piping work.
I would like to use the same code to explain piping.

The basic idea of piping works on the system calls like dup, close and pipe.
pipe()

int pipe(int fd[2]);

pipe() creates a pair of file descriptors, pointing to a pipe inode, and places them in the array pointed to by filedes. fd[0] is for reading, fd[1] is for writing.

close()

int close(int fd);

close() closes a file descriptor, so that it no longer refers to any file and may be reused.

dup()

int dup(int fd);

dup() create a copy of the file descriptor fd

Let’s see how using these system calls, the piping works. For this let’s check a piece of code

int some_fd=open("abc",O_RDONLY);
close(0);
fd=dup(some_fd);
printf("%d\n",fd);

The output of this code is

0

how??
This is because as soon as we close the file descriptor 0, the slot 0 in the file table becomes vacant. So when we issue the dup system call, it duplicates the file descriptor and puts into the slot 0. So from now whatever we write to stdin is written to the file ‘abc’

This idea is utilized in piping in the shell. We create a pipe and create a new process. In the child process, we close stdin and in parent stdout (or vice versa). In both of the processes we also issue the dup call immediately and at the same time close the write end in one and read end in another

Let’s check the program. The program on execution expects two commands given as

$ cmd1 | cmd2

The program

#include <stdio.h>
#include <error.h>
#include <stdlib.h>
#include <string.h>  

#define CMD_LEN 1024    //Maximum length of Command
#define MAX_LEN 1024    //Maximum Length of the input
#define MAX_CMD 2       //Maximum number of Commands
#define ARG_LEN 128     //Maximum Length of Argument
#define ARG_COUNT 64    //Maximum number of arguments

int main(int argc, char *argv[], char *envp[])
{
        while(1){
                char *arg[MAX_CMD][ARG_COUNT];
                char cmd[MAX_LEN];

                char *command[MAX_CMD+1];
                char *pos=&cmd[0];
                int cmd_len;  int pid;  int status;

                printf("\n$ ");           //Displaying the prompt
                fgets(cmd,MAX_LEN,stdin); //Reading user input/
                cmd_len=strnlen(cmd,MAX_LEN);

                int i=0,k;
                do{
                        int len;
                        char a[CMD_LEN];
                        sscanf(pos,"%[^|]",a);//Parsing out the commands
                        len=strnlen(a,CMD_LEN);

                        command[i]=malloc(len);
                        if(!command[i]){
                                printf("Unable to allocate memory\n");
                                exit(1);
                        }

                        strncpy(command[i++],a,CMD_LEN);
                        pos+=(len+1);    //Repositioning for next command
                }while(pos<cmd+cmd_len);

                command[i]=NULL;

                i=0;

                while(command[i]){
                        pos=command[i];
                        int arg_count=0;
                        cmd_len=strnlen(command[i],CMD_LEN);

                        do{
                                int len;
                                char a[ARG_LEN];
                                int count=sscanf(pos,"%s",a);

                                //Parsing out the arguments
                                if(count < 1)
                                        break;

                                len=strnlen(a,ARG_LEN);

                                arg[i][arg_count]=malloc(len);
                                if(!arg[i][arg_count]){
                                        printf("Unable to allocate memory\n");
                                        exit(1);
                                }

                                strncpy(arg[i][arg_count++],a,ARG_LEN);
                                pos+=(len+1);  //Repositioning for next argument
                        }while(pos<command[i]+cmd_len);

                        arg[i][arg_count]=NULL;   i++;
                }

                if(i!=2){
                        printf("Two commands required");
                        continue;
                }

                //Obtained the arguments

                pid=fork();                     //Creating a process to execute command 2

                if(pid==0){
                        int fd[2];
                        if(pipe(fd)==-1){       //Creating a Pipe
                                perror("pipe:");
                                exit(1);
                        }

                        pid=fork();             //Creating process to execute command 1

                        if(pid==0){
                                close(fd[0]);
                                close(1);        //closing stdout

                                if(dup(fd[1])==-1){      //stdout now points to fd[1]
                                        perror("dup");
                                        exit(1);
                                }

                                //In child process
                                if(execve(arg[0][0],&arg[0][0],envp)==-1){
                                        perror("execve");
                                }

                                exit(1); //Reached if Child failed to execute process
                        }

                        else{
                                close(fd[1]);//close the write end
                                close(0);//closing the stdin

                                if(dup(fd[0])==-1){//stdin now points to fd[0]
                                        perror("dup:");
                                        exit(1);
                                }

                                //In parent process
                                if(execve(arg[1][0],&arg[1][0],envp)==-1){
                                        perror("execve");
                                }
                                exit(1); //Reached if failed to execute process
                        }
                }
                wait(&status);    //Wait for process to terminate //continue
        }
        return 0;
}

Output

$  /bin/ls / | /bin/grep root
root

$