Search code examples
bashcurlformatwgetpacman-package-manager

wget or curl: format progress bar


I am writing an update script like that:

update.sh

#!/bin/bash

sudo echo

printf "### PACMAN\n"   # I am running Arch Linux
sudo pacman -Syu

printf "\n### YAY\n"
yay -Syua

printf "\n### CUSTOM\n"
custom_script.sh # code below

custom_script.sh

#!/bin/bash

# [...]

wget --quiet --show-progress <some link>

# [...]

The output looks something like this:

### PACMAN
<...>
:: Retrieving packages...
 linux-lts-4.14.88-1-x86_64                     60,8 MiB   725K/s 01:26 [########################################] 100%
<...>

### YAY
:: Searching AUR for updates...
 there is nothing to do

### CUSTOM
:: Downloading custom updates...
somefile.txt                  100%[================================================>]  20,92M  3,83MB/s    in 5,6s

Is there a way to make the wget command in custom_script.sh format the progress bar the same way as pacman? I'm open to use curl or some other download tool as well.

Desired output:

### PACMAN
<...>
:: Retrieving packages...
 linux-lts-4.14.88-1-x86_64                     60,8 MiB   725K/s 01:26 [########################################] 100%
<...>

### YAY
:: Searching AUR for updates...
 there is nothing to do

### CUSTOM
:: Downloading custom updates...
 somefile.txt                                   20,9 MiB   3,8M/s 00:05 [########################################] 100%

Solution

  • Thanks to the link Inian provided (https://github.com/roddhjav/progressbar), a similar approach as hanshenrik and a lot of googling and trial-and-error, I came up with the following solution:

    custom_script.sh

    #!/bin/bash
    
    source progressbar.sh || exit 1
    
    reformat_and_output_line() {
        COLS=()
        for val in $1 ; do
                COLS+=("$val")
        done
        if [[ ${#COLS[@]} == 9 ]]; then
            RELATIVE_PROGRESS=${COLS[6]%?}
            ABSOLUTE_PROGRESS=$(numfmt --from=iec --to=iec-i --format "%.1fB" ${COLS[0]} |
                                sed 's/\([^[:blank:]]\)\([[:upper:]]\)/\1 \2/')
            SPEED=$(printf "%+8s" "${COLS[7]}/s")
            TIME=$(printf "%+5s" ${COLS[8]})
            progressbar "somefile.txt" "$RELATIVE_PROGRESS" 100 "$ABSOLUTE_PROGRESS" "$SPEED" "$TIME"
        elif [[ ${#COLS[@]} == 7 ]]; then
            RELATIVE_PROGRESS=${COLS[5]%?}
            ABSOLUTE_PROGRESS=$(numfmt --from=iec --to=iec-i --format "%.1fB" ${COLS[0]} |
                                sed 's/\([^[:blank:]]\)\([[:upper:]]\)/\1 \2/')
            SPEED=$(printf "%+8s" "$(echo ${COLS[6]} | cut -d= -f1 )/s")
            TIME=$(printf "%+5s" $(echo ${COLS[6]} | cut -d= -f2 ))
            progressbar "somefile.txt" "$RELATIVE_PROGRESS" 100 "$ABSOLUTE_PROGRESS" "$SPEED" "$TIME"
        fi
    }
    
    wget_like_pacman() {
        last_output_time=$(( $(date +%s%3N) - 500 ))
        wget --quiet --show-progress $1 2>&1 | (
            while IFS= read -r line; do
                if [[ $(date +%s%3N) > $(( $last_output_time + 500 )) ]]; then
                    reformat_and_output_line "${line}"
                    last_output_time=$(date +%s%3N)
                fi
            done
            printf "\r"
            reformat_and_output_line "${line}"
            echo
        )
    }
    
    # [...]
    
    wget_like_pacman <some link>
    
    # [...]
    

    The time format is not the exact same, but apart from that, I think this is pretty accurate.

    One can also omit the if block checking the last_output_time, but then the terminal goes crazy and there is no way to actually read any of the values since they are updated too fast.