A progress bar for Bash scripts

When copying large files in Bash shell scripts, it would be nice to have a progress bar displayed. Unfortunately the cp command does not have a progress bar option.

The following script shows how we can implement a copy function in Bash that displays a progress bar with ETA (Estimated Time to Arrival). It simulates the cp command by piping the stdin and stdout of cat, dd and cat in series. The dd command is encapsulated in a while loop so that it can perform the copying in chunks of 1MB and also to print the progress bar. Note that the progress bar is printed on stderr as stdin and stdout are used to transfer the bytes.

copy() { # src dst [width]
    srcsize=$(stat -c %s $1) || return $?
    dstsize=0
    width=${3:-25}
    mega=$(( 1024 * 1024 ))
    start=$(date +%s)
    cat $1 | (
    while [[ dstsize -lt srcsize ]]
    do
        dd bs=512 count=2048 2>/dev/null || return $?
        (( dstsize += $mega ))
        [[ dstsize -gt srcsize ]] && dstsize=$srcsize

        # print truncated filename
        name=$(basename $1 | cut -b -20)
        printf "\r%-20s " $name 1>&2

        # print percentage
        percent=$(( 100 * $dstsize / $srcsize ))
        printf "%3d%% [" $percent 1>&2

        # print progress bar
        bar=$(( $width * $dstsize / $srcsize ))
        for i in $(seq 1 $bar); do printf "=" 1>&2; done
        for i in $(seq $bar $(( $width-1 ))); do printf " " 1>&2; done

        # print size of file copied
        if [[ $dstsize -le 1024 ]]; then
            printf -v size "%d" $dstsize;
        elif [[ $dstsize -le $mega ]]; then
            printf -v size "%d kB" $(( $dstsize / 1024  ));
        else
            printf -v size "%d MB" $(( $dstsize / $mega ));
        fi
        printf "] %7s" "$size" 1>&2

        # print estimated time of arrival
        elapsed=$(( $(date +%s) - $start ))
        remain=$(( $srcsize - $dstsize ))
        eta=$(( ($elapsed * $remain) / $dstsize + 1))
        if [[ $remain == 0 ]]; then eta=0; fi
        etamin=$(( $eta / 60 ))
        etasec=$(( $eta % 60 ))
        if [[ $eta > 0 ]]; then etastr="ETA"; else etastr="   "; fi
        printf "   %02d:%02d $etastr" $etamin $etasec 1>&2
    done
    echo 1>&2
    ) | cat >$2
}

The inspiration for this code came from studying the progress bar implemented at http://www.theiling.de/projects/bar.html. The major differences of the above code from bar are in the progress bar format and the use of the extra cat command at the beginning. The above code is also less generic but that’s fine with me since it is simpler and shorter.

To use the above code, simply cut and paste it into the top of your shell script. Then wherever you have cp srcfile dstfile replace it with copy srcfile dstfile to see the progress bar. Note that the copy function is not a drop-in replacement for cp since it handles neither options nor wildcards.

Advertisements
Posted in hacks. Tags: , . 12 Comments »