Skip to content

Instantly share code, notes, and snippets.

@amullins83
Last active March 29, 2024 15:04
Show Gist options
  • Save amullins83/24b5ef48657c08c4005a8fab837b7499 to your computer and use it in GitHub Desktop.
Save amullins83/24b5ef48657c08c4005a8fab837b7499 to your computer and use it in GitHub Desktop.
A simple command-line progress bar in C

#Progress in C

This is in an example meant to present some ideas regarding command-line progress bars in C.

##Important parts

The main idea is to overwrite stdout with new information every time a particular step is reached. I accomplished this using the VT100 emulator hack from this Stack Overflow answer. In a nutshell, printing ^[[2K to stdout erases the current line, but it doesn't necessarily move the cursor to the start. Printing \r does that. Also, I decided I wanted to print a newline character after the progress indicator, but I need to get rid of that newline on the next print. That's what \b does: it inserts a backspace, deleting the last character printed.

Also important is the call to fflush, which will guarantee that the print operation completes and is visible before the program moves on to its next task (see this Stack Overflow answer).

##Less important parts

I decided to let the progress function manage its own progress buffer, which is dynamically allocated on each call to print_progress. An alternative would be to pass in a buffer that is allocated elsewhere along with a size parameter. Then the print_progress function would need to make sure not to overrun the buffer.

I also decided to go ahead and print an extra newline character in main before the first print_progress call. Otherwise the first \b printed would delete the newline from the command prompt, which is allowed but not usually expected.

I borrowed a platform-independent my_wait function from this Stack Overflow answer to test tracking progress with long-running function calls.

#include <stdio.h>
#include <stddef.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
void
my_wait(size_t secs)
{
#ifdef _WIN32
Sleep(1000 * secs);
#else
sleep(secs);
#endif
}
void
print_progress(size_t count, size_t max)
{
const char prefix[] = "Progress: [";
const char suffix[] = "]";
const size_t prefix_length = sizeof(prefix) - 1;
const size_t suffix_length = sizeof(suffix) - 1;
char *buffer = calloc(max + prefix_length + suffix_length + 1, 1); // +1 for \0
size_t i = 0;
strcpy(buffer, prefix);
for (; i < max; ++i)
{
buffer[prefix_length + i] = i < count ? '#' : ' ';
}
strcpy(&buffer[prefix_length + i], suffix);
printf("\b%c[2K\r%s\n", 27, buffer);
fflush(stdout);
free(buffer);
}
#define NUM_SECS 10
int
main()
{
size_t num_secs = 1, max_secs = NUM_SECS;
printf("%s\n", "");
print_progress(0, max_secs);
for (; num_secs <= max_secs; ++num_secs)
{
my_wait(1);
print_progress(num_secs, max_secs);
}
return 0;
}
@gabrieldurante
Copy link

Memory allocation is bad for big sizes of tasks and it'll consume a lot of terminal space... A good option is to normalize it to 100...

#define PERCENTAGE(V, T) (100 - (((T - V) * 100) / T))

char *buffer = calloc(100 + prefix_length + suffix_length + 1, 1);

for (; i < 100; ++i)
{
buffer[prefix_length + i] = i < PERCENTAGE(count, max) ? '#' : ' ';
}

@amullins83
Copy link
Author

👍

@amansahu278
Copy link

When i tried using
printf("\b%c[2K\r%s\n", 27, buffer);
The result was rather odd when the wait time was large. It was being printed on a new line each time and it didn't look as "pretty".
Using
printf("\b\r%c[2K\r%s", 27, buffer);
gave much more satisfying result.
But thank you so much for the help!

@SilvaneUX
Copy link

SilvaneUX commented Jun 6, 2021

for people who want to combine methods from @mrgabu and @amansahu278 with the original code from @amullins83 . Just in case someone got error: first declared var, Here is some fix and ready to use code.

my first try to combine was failed so I add int i; and it worked for me

here is the final code :

#include <stdio.h>
#include <stddef.h>
#define PERCENTAGE(V, T) (100 - (((T - V) * 100) / T))
#ifdef _WIN32
	#include <windows.h>
#else
	#include <unistd.h>
#endif

void
my_wait(size_t secs)
{
	#ifdef _WIN32
		Sleep(1000 * secs);
	#else
		sleep(secs);
	#endif
}

void
print_progress(size_t count, size_t max)
{
	int i;
	const char prefix[] = "Progress: [";
	const char suffix[] = "]";
	const size_t prefix_length = sizeof(prefix) - 1;
	const size_t suffix_length = sizeof(suffix) - 1;
	char *buffer = calloc(100 + prefix_length + suffix_length + 1, 1);

	strcpy(buffer, prefix);
	for (; i < 100; ++i)
	{
		buffer[prefix_length + i] = i < PERCENTAGE(count, max) ? '#' : ' ';
	}

	strcpy(&buffer[prefix_length + i], suffix);
	printf("\b\r%c[2K\r%s", 27, buffer);
	fflush(stdout);
	free(buffer);
}

#define NUM_SECS 10

int
main()
{
	size_t num_secs = 1, max_secs = NUM_SECS;
	printf("%s\n", "");
	print_progress(0, max_secs);
	for (; num_secs <= max_secs; ++num_secs)
	{
		my_wait(1);
		print_progress(num_secs, max_secs);
	}

	return 0;
}

update :
Thanks to all users I mentioned before. It's really helped me out to finish my college final project.

@Yusufss4
Copy link

Yusufss4 commented May 1, 2023

Because that, that is one of the first search results when progress bar made with C. Here is a very simple, inline print_progress function that I used and worked perfectly.

#include <stdio.h>

void print_progress(size_t count, size_t max) {
    const int bar_width = 50;

    float progress = (float) count / max;
    int bar_length = progress * bar_width;

    printf("\rProgress: [");
    for (int i = 0; i < bar_length; ++i) {
        printf("#");
    }
    for (int i = bar_length; i < bar_width; ++i) {
        printf(" ");
    }
    printf("] %.2f%%", progress * 100);

    fflush(stdout);
}

And here is how it looks. It overwrites the old loading bar.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment