Search code examples
pythonfabriccarriage-return

Handling of carriage return (\r) character in Python's Fabric


I am using Python's Fabric to execute some tasks on remote hosts through ssh.
One of the tasks uses carriage return character in its output, when displaying progress-bars of sub-tasks. Something similar to this:

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

        int i;

        for (i = 0; i <= 5; ++i) {
                printf("\rProgress: %d%%", (int)(100 * ((float)i / 5)));
                fflush(stdout);
                sleep(1);
        }
        printf("\n");

        return 0;
}

When this runs with python's Fabric:

def some_task():
    with settings(user = 'root'):
        run('./task_with_progress_bar.out')

tasks.execute(some_task, hosts = ['server_name'])

Fabric will ignore the \r, and instead of having the progress bar in one line, what I'll get is:

[server_name] Executing task 'some_task'
[server_name] run: ./task_with_progress_bar.out
[server_name] out:
[server_name] out: Progress: 0%
[server_name] out: Progress: 20%
[server_name] out: Progress: 40%
[server_name] out: Progress: 60%
[server_name] out: Progress: 80%
[server_name] out: Progress: 100%
[server_name] out:

It seems like \r is being treated as \n...

It the example above it's not too bad, but the actual task I am running is filled with progress bars that progress with smaller steps, and this pollutes the entire console with unnecessary output.

Is there a way to somehow customize the output so that Fabric will not ignore the \r, but still keep the same general format of: [<server_name>] <action_being_taken>? I like this format, it's neat and clean.


Solution

  • The problem is that fabric is transforming \r in \n.

    A workaround to this problem is to create a file like class and pass it as stdout argument to run, so we can process the output produced by the called program and translate \n back to \r. The challenge of writing such class is to identify which \n must be translated to \r. Here is attempt that looks for "Progress:" to identify such cases:

    class FixProgress:
        def __init__(self):
            self.saw_progress = False
    
        def write(self, s):
            if 'Progress:' in s:
                self.saw_progress = True
            if s == '\n' and self.saw_progress:
                sys.stdout.write('\r')
                self.saw_progress = False
            else:
                sys.stdout.write(s)
    
        def flush(self):
            sys.stdout.flush()
    
    def some_task():
        with settings(user = 'root'):
            run('./task_with_progress_bar.out', stdout = FixProgress())
    

    This worked correctly with the c program in the question.