Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

A Typical Linux C Application: Standard I/O ports allowing simple IPC pipe based

4.61/5 (10 votes)
16 Oct 2008Public Domain4 min read 1   448  
Demonstrates how to create a simple application that interacts with terminal standard I/O ports to provide an IPC pipe based between processes

Image 1

Figure 1: Piping makeupper with 2 other processes

Introduction

A typical Linux (or UNIX) terminal application, like cat command, provides three ways to input data:

  • By passing a file name as argument: cat file.txt <enter>
  • By entering in canonical mode: cat <enter>
    • Canonical mode is a terminal input mode processed in units of lines. That means the program reads the information after a new line (CRLF) or EOF (End-Of-File) character has been entered.
  • By creating a pipe connection to send data to a process (or receive from it):

    cat file.txt | more <enter>

This article demonstrates how to create a small application (makeupper) that allows those three ways of inputting data.

Note: The source code should compile without errors in every UNIX flavor or Linux distribution.

Terminal Input/Output

In Linux and all other UNIX flavors (like IBM-AIX), there is no difference between reading data from a file and reading data from a terminal or between writing data to a file and writing data to screen - specially if the data consists only of strings of characters.

A terminal provides three I/O ports:

  • standard input: by default is the keyboard
  • standard output: by default is the terminal screen
  • standard error: by default is the terminal screen

There are two sets of functions to handle those I/O ports:

  • The low-level I/O system calls: that use file descriptors to access standard input, output and error.
  • The standard I/O library: that use streams implemented as a pointer to FILE* structure (#include <stdio.h>).

Image 2

Figure 2: Low-level system calls and standard I/O library functions

Note: You can get more information about low-level system calls: read, write, open, select, close.

The source code contains a small application named test.c that illustrates the use of low-level system calls and standard I/O library functions by printing a string to terminal screen (standard output). The source code is the simplest possible:

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

int main ( int _argc, char *_argv[] )
{
    char *ptr = "This is an output test!\n";

    printf( ptr );
    fprintf( stdout, ptr );
    write( 1, ptr, strlen(ptr) );

    return 0;
}

To build test.c by using the C compiler:

cc -o test test.c

When you execute ./test, you get the following output:

Image 3

Figure 3: ./test output

You see? The string "This is an output test!" pointed by ptr is printed to screen (standard output) by using three different functions:

  • printf: Part of standard I/O library that by default always prints a string to standard output
  • fprintf: Part of standard I/O library. Notice the first argument, stdout stream redirect ptr string to standard output (or screen).
  • write: Low-level system call. Notice the first argument, 1 which is the file descriptor for standard output.

Makeupper Sample

There are two more samples in the source code: makeupper.c and makeupper2.c. They do the same thing. The difference is that makeupper.c uses low-level system calls and makeupper2.c uses standard I/O library.

makeupper sample is very simple. Its purpose is to capitalize letters in inputted data and print to standard output. However, it demonstrates how to read from standard input and from a file too. The first sample, makeupper.c, uses low-level system calls:

C++
/* Makeupper.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

int main ( int _argc, char *_argv[] )
{
    int  fd_main = 0;
    char ch = 0;

    fd_main = _argc == 1 ? 0 : open(_argv[1], O_RDONLY);

    if ( fd_main == -1 )
    {
       perror("input");
       exit(-1);
    }

    while ( read( fd_main, &ch , 1 ) )
    {
       ch = toupper(ch);
       write( 1, &ch, 1 );
    }

    close(fd_main);

    return 0;
}

Notice the line that reads:

C++
fd_main = _argc == 1 ? 0 : open(_argv[1], O_RDONLY);

If no file path argument is passed then fd_main assumes standard input descriptor. open system call is used to open a file when it is passed as argument.

makeupper2.c uses standard I/O library functions instead of system calls:

C++
/* Makeupper2.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main ( int _argc, char *_argv[] )
{
    FILE *f_file = NULL;
    char ch = 0;

    f_file = _argc == 1 ? stdin : fopen(_argv[1], "r");

    if ( f_file == NULL )
    {
       perror("input");
       exit(-1);
    }

    while ( !feof(f_file) )
    if ( (ch = fgetc(f_file)) > 0 )
        fputc( toupper(ch), stdout );

    fclose(f_file);

    return 0;
}

To build both samples:

  • cc -o makeupper makeupper.c
  • cc -o makeupper2 makeupper2.c

By executing ./makeupper (or ./makeupper2) without arguments, it starts in Canonical Mode. After you press enter, makeupper receives the line. To quit Canonical Mode, you should press CTRL+D or CTRL+\:

Image 4

Figure 4: ./makeupper - canonical mode

You can also pass a file path:

Image 5

Figure 5: ./makeupper - printing test.c contents to standard output

Or makeupper can receive the output from a process in its standard input:

Image 6

Figure 6: ./makeupper2 - ls -ltr output is redirected from screen (standard output) to standard input of makeupper2 (via PIPE)

Redirecting I/O

You can redirect standard output and error to files or devices (/dev/...). To illustrate that type and compile the following code (you can name it test2.c):

C++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main ( int _argc, char *_argv[] )
{
    char *p1 = "printed to standard output via stdlib.\n";
    char *p2 = "printed to standard error via stdlib.\n";
    char *p3 = "printed to standard output via low-level calls.\n";
    char *p4 = "printed to standard error low-level calls.\n";

    fprintf( stdout, p1 );
    fprintf( stderr, p2 );

    write ( 1, p3, strlen(p3) );
    write ( 2, p4, strlen(p4) );

    return 0;
}

There are four strings pointed for four pointers. p1 and p3 are printed to standard output and p2 and p4 are printed to standard error.

To build it:

cc -o test2 test.c

By executing ./test2, we have:

Image 7

  1. ./test2 - you get all strings printed to screen.
  2. ./test2 >file.txt - redirects standard output to file.txt. Notice only p1 and p3 strings are saved in file.txt.
  3. ./test2 2>file.txt redirects standard error to file.txt. Notice only p2 and p4 strings are saved in file.txt.
  4. ./test2 >file.txt 2>&1 redirects both output and error to file.txt. Notice the trick 2>&1 (means redirect standard error to standard output).

If you want to append to existing file: ./test2 >>file.txt or ./test2 >>file.txt 2>&1.

Hope this helps.

History

  • 14th October, 2008: First version

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication