views:

312

answers:

7

Hi,

I was writing a command line program which will have a status bar, much like wget.

The main problem I'm facing is: how do I delete what I've already sent into stdout/stderr?

I had on idea: use the backspace char '\b' and erase the output I've sent. Is that the best way? Is it the only way? Is there a better way?

PS: I don't want to use anything like ncurses. Plain old C please.

Thanks


EDIT:

Can I also go up and/or down? Example: I have 10 lines of output, I want to change the 3rd line from Doing ABC to ABC: Done. How can I do that?

Also, can anyone post more details about what VT102 characters are? What are its capabilities? Please post good links on this if you have any.

Thanks

A: 

It is a progressbar for bash.

function gauge()
{
        progress="$1"
        total="$2"
        width=`tput cols`
        let gwidth=width-7

        if [ "$total" == "0" ]; then
                percent=100
        else
                set +e
                let percent=progress*100/total;
                set -e
        fi

        set +e
        let fillcount=percent*gwidth/100
        let nofillcount=gwidth-fillcount
        set -e

        fill="";
        if [ "$fillcount" -gt "0" ]; then
                for i in `seq $fillcount`; do
                        fill="$fill""|"
                done
        fi;
        nofill=""
        if [ "$nofillcount" -gt "0" ]; then
                for i in `seq $nofillcount`; do
                        nofill="$nofill"" ";
                done
        fi
        echo -e -n "\r[""$fill""$nofill""] ""$percent""%";
}
Notinlist
dont want for bash. want for c. no bash. only c. c.
Here Be Wolves
Or... you could read it, and then think about what you read.You might realize that it tells you how it works, and with a little more thought you'd be able to come up with your own c implementation.Just a thought.
James Caccese
+1  A: 

A slight variation on your own solution:

You can also print a carriage return (\r), which will return you to the start of the line.

Shtééf
+2  A: 

Use '\r' to return to the beginning of the line and possibly rewrite the whole line.

Look for VT102 control sequences - these are character sequences ESC ... to control your terminal.

your answer address my original question completely, though I'm tempted to ncurses, now that I've read John's answer..
Here Be Wolves
+1  A: 

There is also the possiblity of using Ncurses, which is a library for Textual UI, where this kind of behaviour should have some support. However, it may be overkill for something like this.

Tobias Wärre
As correct as your answer is, the asker has decided that they'd rather not use it.
Ignacio Vazquez-Abrams
+2  A: 

The basic formatting control characters are backspace (\b), tab (\t), newline (\n), and carriage return (\r). If you need more than that then you can use ANSI X3.64 / ISO/IEC 6429 / ECMA-48 escape sequences; at least the VT100 subset is recognized by most modern terminals and emulators. An advantage of using ncurses is that it will look up the capabilities of your particular terminal and so it will work even if your terminal uses a different set of escape sequences.

mark4o
+1: Best answer; should have been accepted.
Dan Moulding
+3  A: 

You have to remember that as far as the regular stdio routines are concerned, stdout is just a byte stream with no inherent display characteristics; that depends on the target device, which can be anything from a regular VT100-style terminal to a hardcopy terminal to a sheet-fed printer to a plotter to whatever.

IMO, you're far better off using a library like ncurses than trying to hack together your own display management code with VT100 escape codes, even for a relatively simple task like this. I know you want to stick with "plain old C", but this is a task that falls outside the bounds of plain old C.

John Bode
How do you know the output tty understands vt100 codes? Agree with the curses(3) (or variant) library. For something like this, it's pretty simple
mpez0
A: 

About the progress bar: something like this?

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

typedef enum
{
    false=0,
    true=!false
} bool;

typedef struct
{
    /* Start delimiter (e.g. [ )*/
    char StartDelimiter;
    /* End Delimiter (e.g. ] )*/
    char EndDelimiter;
    /* Central block (e.g. = )*/
    char Block;
    /* Last block (e.g. > ) */
    char CurBlock;
    /* Width of the progress bar (in characters) */
    unsigned int Width;
    /* Maximum value of the progress bar */
    double Max;
    /* True if we have to print also the percentage of the operation */
    bool PrintPercentage;
    /* True if the bar must be redrawn;
       note that this must be just set to false before the first call, the function then will change it by itself.  */
    bool Update;
} ProgressBarSettings;

/* Prints/updates the progress bar */
void PrintProgressBar(double Pos, ProgressBarSettings * Settings);
/* Inits the settings of the progress bar to the default values */
void DefaultProgressBar(ProgressBarSettings * Settings);

int main()
{
    int i;
    /* Init the bar settings */
    ProgressBarSettings pbs;
    DefaultProgressBar(&pbs);
    pbs.Max=200;
    pbs.Width=60;
    printf("Progress: ");
    /* Show the empty bar */
    PrintProgressBar(0,&pbs);
    for(i=0;i<=pbs.Max;i++)
    {
        /* Wait 50 msec */
        usleep(50000);
        /* Update the progress bar */
        PrintProgressBar(i,&pbs);
    }
    puts(" Done");
    return 0;
}

/* Inits the settings of the progress bar to the default values */
void DefaultProgressBar(ProgressBarSettings * Settings)
{
    Settings->StartDelimiter='[';
    Settings->EndDelimiter=']';
    Settings->Block='=';
    Settings->CurBlock='>';
    Settings->PrintPercentage=true;
    Settings->Update=false;
    Settings->Max=100;
    Settings->Width=40;
}

/* Prints/updates the progress bar */
void PrintProgressBar(double Pos, ProgressBarSettings * Settings)
{
    /* Blocks to print */
    unsigned int printBlocks=(unsigned int)(Settings->Width*Pos/Settings->Max);
    /* Counter */
    unsigned int counter;
    /* If we are updating an existing bar...*/
    if(Settings->Update)
    {
        /* ... we get back to its first character to rewrite it... */
        for(counter=Settings->Width+2+(Settings->PrintPercentage?5:0);counter;counter--)
            putchar('\b');
    }
    else
        Settings->Update=true; /* next time we'll be updating it */
    /* Print the first delimiter */
    putchar(Settings->StartDelimiter);
    /* Reset the counter */
    counter=Settings->Width;
    /* Print all the blocks except the last; in the meantime, we decrement the counter, so in the end we'll have
       the number of spaces to fill the bar */ 
    for(;printBlocks>1;printBlocks--,counter--)
        putchar(Settings->Block);
    /* Print the last block; if the operation ended, use the normal block, otherwise the one for the last block */
    putchar((Settings->Max==Pos)?Settings->Block:Settings->CurBlock);
    /* Another block was printed, decrement the counter */ 
    counter--;
    /* Fill the rest of the bar with spaces */
    for(;counter;counter--)
        putchar(' ');
    /* Print the end delimiter */
    putchar(Settings->EndDelimiter);
    /* If asked, print also the percentage */
    if(Settings->PrintPercentage)
        printf(" %3d%%",(int)(100*Pos/Settings->Max));
    /* Flush the output buffer */
    fflush(stdout);
};

Note: the unistd.h and usleep thing is just to fake the progress of an operation, the progress bar code itself just uses the standard library. Its only assumptions about the output stream are that \b actually gets to the previous written character. I tried it successfully on Windows and Linux (with gnome-terminal), don't know if it doesn't work correctly with some terminal emulators. Sorry for the excessive amount of comments, I wrote it for another forum where I needed to explain pratically every line of the code to a C newbie.

Matteo Italia