0

The follow bash script works fine for for high counting number ranges from 1..1000

By high counting number ranges, bigger than 1..1000000, it needs some time to start. In general, it's working fine.

for i in {1..10}; do
    printf '\r%2d' $i
    sleep 1
done
printf '\n'

For counting down number ranges from 99..1 it's working fine.

For counting down number ranges which start higher than 99, p.e. 100..1 the output prints a digit with a zero too much. How can you avoid that?

Kusalananda
  • 333,661
Alfred.37
  • 204
  • 1
  • 5
  • 23

3 Answers3

1

The problem you're seeing is that you're first writing 100 and then 99 and finally 1 starting from the same place, but you've specified only two digits in your output format %2d.

You've not provided any erase instruction so here's what you see - but all on one line

What       # What
you        # was
see        # output


100        # 100
990        # 99
980        # 98
...
 90        # 9
 80        # 8
...
 10        # 1

You should either format with three digits or suffix a space:

printf "\r%3d" $i     # One option
printf "\r%2d " $i    # Another option
Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • If I let the counter count up, because of me 1..1000000 the problem does not exist. It only exists in the direction of 1000000..1

    Unfortunately I don't have enough knowledge to fix this myself. Best would be a fix, where the exact number of digits of the numbers does not matter.

    – Alfred.37 May 19 '20 at 17:49
  • Its looks like the follow works for bigger ranges, with a universal number of caracters. printf "\r%2d " $i # Another option – Alfred.37 May 19 '20 at 18:07
  • Yet, how can I replace either of the range endpoints with a variable on follow example: for i in {1..10}; do printf '\r%2d' $i sleep 1 done printf '\n' – Alfred.37 May 20 '20 at 09:42
1

Your issues:

  1. It takes some time for the command to start. This is due to you using a brace expansion for the loop. The expansion must first be expanded, meaning the shell must create a list containing each and every number in the range that you want to loop over. This would take time (to create the list) and memory (to store the list). On my machine, I can see the bash process grow from about 1400 KiB to 256 MiB when I ask it to create the list {1..1000000}.

    Instead, consider using an arithmetic loop,

    for (( i=1000000; i >= 1; --i )); do ...; done
    

    or a POSIX-compatible loop,

    i=1000000
    while [ "$i" -ge 1 ]; do ...; i=$(( i - 1 )); done
    

    Both of these would, instead of looping over a static list of numbers, test the value $i against 1, run the loop body if the test succeeded, and then decrement the value of the variable i, in each iteration.

  2. You get "extra zeros" at the end of each number. This is because you move the cursor back to the start of the line by outputting a carriage-return character. This moves the cursor, but it will not clear the line, so the end of the last number outputted will still be visible if the new number has less digits.

    To sort this out, you could try using a VT100 escape code for clearing the whole line (\e[2K) before moving the cursor to the start of the line:

    for (( i=1000000; i >= 1; --i )); do
        printf '\e[2K\r%d' "$i"
        sleep 1
    done
    printf '\n'
    

    \e[1K would also work as it clears the area of the line to the left of the cursor. Alternatively use \r%d\e[0K to clear the line to the right of the cursor after the number has been outputted.

Kusalananda
  • 333,661
0

If you want to replace an string with another you need both to be of the same size (or longer with non-printing characters (spaces?)).

If you replace an already printed 123 with 7, you get 723 or 127 depending if you print at the start or at the end.

To do this (strings of same size) you could do either:

  • Use a brace expansion of an stable size: {000..123}
  • Use a printf that fills the whole space: printf '\r%5s' "$i"
  • Print a long string of spaces : printf '\r%s ' "$i"
  • Clear the line up to its end: printf '\r%s\e[K' "$i"
  • Clear the whole line and then print value: printf '\e[2K\r%s' "$i"

But using {1..1000000} is not the best solution as it generates a long string of numbers that need to be kept in memory, then divided on the spaces and then used in the loop. A better solution in bash is:

for(( i=1; i<1000; i++ )); do
    printf '\r%5d' $i
    sleep 0.01
done
echo