Showing posts with label Unix. Show all posts
Showing posts with label Unix. Show all posts

Thursday, 21 May 2015

[Linux] A progress bar implemented in C on Linux and MacOS X

A progress bar implemented in C on Linux and MacOS X

Implementations

  • CR - carriage return (\r)
  • CSI_1 - move up one row and back to column 1, and then erase display from cursor to end of display (\033[1F\033[J)
  • CSI_2 - move up one row and back to column 1, and then erase display from start of display to cursor (\033[1F\033[1J)
  • CSI_3 - move up one row and then erase whole line (\033[1A\033[2K)
CSI Sequence Reference:

Source code


// An implementation of progress bar in C on Linux
//
// Usage:
//    ./prog [type]
//        type: CR, CSI_1, CSI_2, CSI_3
//
// Bar style: 
//  0 - 10% [====                                                 ] 
//  1 -     |====.................................................| 10%
//  2 -     [||||.................................................] 10%
//
// Implementations
//  CR    - carriage return (\r) 
//  CSI_1 - move up one row and back to column 1, and then 
//          erase display from cursor to end of display (\033[1F\033[J)
//  CSI_2 - move up one row and back to column 1, and then 
//          erase display from start of display to cursor (\033[1F\033[1J)
//  CSI_3 - move up one row and then erase whole line (\033[1A\033[2K)
//  
//  Reference for CSI sequence: man console_codes(4)
//

#include 
#include 
//#include  // usleep deprecated
#include    // nanosleep
#include    // strcmp

//#define BAR_STYLE   0
//#define BAR_STYLE   1
#define BAR_STYLE   2

typedef enum
{
    T_CR,
    T_CSI_1,
    T_CSI_2,
    T_CSI_3,
}TYPE_OF_CONTROL;

void usage()
{
    printf("./prog [type]\n");
    printf("    type: CR, CSI_1, CSI_2, CSI_3\n");
}

void prog_bar (int done, int all, int width, TYPE_OF_CONTROL t)
{
    float ration = (done*100)/(float)all;
    int d = (int)(ration * width)/100;
    int i;

#if BAR_STYLE == 0
    printf("%d%% [", (int)ration); 
    for(i = 0; i < d; i++)
        printf("=");

    for(i = d; i < width; i++)
        printf(" ");

    printf("]"); 
#elif BAR_STYLE == 1
    printf("|"); 
    for(i = 0; i < d; i++)
        printf("=");

    for(i = d; i < width; i++)
        printf(".");

    printf("| %d%% ", (int)ration); 
#elif BAR_STYLE == 2
    printf("["); 
    for(i = 0; i < d; i++)
        printf("|");

    for(i = d; i < width; i++)
        printf(".");

    printf("] %d%% ", (int)ration); 
#endif
    switch(t)
    {
        case T_CR: 
            printf("\r");
            fflush(stdout);
            break;
        case T_CSI_1:
            // move cursor up for 1 line and then erase from cursor to end of display 
            printf("\n\033[1F\033[J");
            break;
        case T_CSI_2:
            // move cursor up for 1 line and then erase from start of display to cursor
            printf("\n\033[1F\033[1J");
            break;
        case T_CSI_3:
            // move cursor up for 1 line and then erase whole line. 
            printf("\n\033[1A\033[2K");
            break;
        default:
            break;
    }
}

int main(int argc, char *argv[])
{
    int i = 0;
    const struct timespec t = {0, 100000000}; //100ms
    TYPE_OF_CONTROL ctrl_type = T_CR;

    if(0 == isatty(1))
    {
        printf("Not a terminal.\n");
        fflush(stdout);
    }

    switch (argc)
    {
        case 1:
            ctrl_type = T_CR;
            break;
        case 2:
            if (strcmp(argv[1], "CR") == 0)
            {
                ctrl_type = T_CR;
            }
            else if (strcmp(argv[1], "CSI_1") == 0)
            {
                ctrl_type = T_CSI_1;
            }
            else if (strcmp(argv[1], "CSI_2") == 0)
            {
                ctrl_type = T_CSI_2;
            }
            else if (strcmp(argv[1], "CSI_3") == 0)
            {
                ctrl_type = T_CSI_3;
            }
            else
            {
                usage(); 
                exit(-1);
            }
            break;
        default:
            usage();
            exit(-1);
    }

    for(i = 0; i <= 100; i++)
    { 
        if(isatty(1))
        { 
            prog_bar(i, 100, 100, ctrl_type); 
        } 
        nanosleep(&t, NULL); //0.1 sec
    }

    exit(0);
}

Test

MacOS X

Different terminal emulators
Apple Terminal
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is xterm-color
iTerm2
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is xterm-color
xterm
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is xterm

Ubuntu

virtual terminal (tty)
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is linux
gnome-terminal
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is gnome-terminal
xterm
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is xterm
ssh from iTerm2 on MacOS X
  • Works: CR, CSI_2, CSI_3
  • Doesn’t work: CSI_1
  • $echo $TERM is xterm-color
ssh from xterm on MacOS X
  • Works: CR, CSI_1, CSI_2, CSI_3
  • $echo $TERM is xterm
​

Tuesday, 19 May 2015

[Unix] a potential usage of script(1) command - test script for progress bar

script(1) command

script(1) command on Unix or Linux aims to make typescript of terminal session (Reference: Unix Help or man 1 script ). Its implementation is based on pseudoterminal. Here’s a diagram that shows how script is implemented.
For example, $script -c "echo helloworld" will run echo helloword to print helloword on terminal (stdout). In addition, it will produce a typescript file that is a copy of everything printed on terminal of the command. In this case, typescript likes below
    Script started on Tue 19 May 2015 10:34:26 BST
    helloworld

    Script done on Tue 19 May 2015 10:34:26 BST

one use case

Scenario

If we have a command that will show a progress bar (like scp) and we need to write a test case for this command to test if the progress bar works as expected, we can not easily capture its output by redirecting its stdout to a file because usually this command will check if the file descriptor for output is a terminal or not by isatty function.
if(isatty(fd))
{
// show a progress bar
}
With redirection like $cmd > file, stdout points to a file. Therefore, isatty function will return 0. Finally progress bar is hidden. That’s not what we expect.

Solution

script -c "cmd" | sed "s/\\r/\\n/g" > prog.txt
With script, the output of cmd is still a terminal (stdout) and so we can capture its output. Besides, through sed, we replace carriage return (\r) by line feed (\n), and then every print by cmd can be captured.
Now prog.txt will record the output of progress bar in this cmd command. That’s what we expected.
​