Thank you for a fun puzzle! Spoiler is at the end, but I thought it might be helpful to look over my shoulder while I poked at the problem. ️ If you're more interested in the answer than the journey, feel free to scroll. I'll never know, anyway.
Following my own advice (see 1st comment beneath the question), I set out to create a small, self-contained, complete example. But, as they say in tech support: Before you can debug the problem, you need to debug the customer. (No offense; I'm a terrible witness myself unless I know ahead of time that someone's going to need to reproduce a problem I've found.)
I interpreted your comment about checking for Internet to mean "it worked before I added the ping
and failed afterward," so the most sensible course of action seemed to be commenting out that part of the code... and then it worked! So what happens differently when the ping
is added?
Changes in timing wouldn't make sense, so the problem must be that ping
generates output that gets piped to zenity
. So I changed the command to redirect its output to the bit bucket:
ping -c 3 -W 3 gcr.io
&>/dev/null
;
...and that worked, too! Interesting!
I explored what turned out to be a few ratholes:
- I ran
ping
from the command line and piped its output through od -xa
to check for weird control characters, but nope.
- Instead of enclosing the contents of the
if
block in parentheses (()), which executes the commands in a sub-shell, I tried braces ({}) to execute them in the same shell. Nope, again.
- I tried a bunch of other embarrassingly useless and time-consuming ideas. Nope, nope, and nope.
Then I realized I could just do
ping -c 3 -W 3 gcr.io | zenity --progress --auto-close
directly from the command line. That failed with the --auto-close
flag but worked normally without it. Boy, did that simplify things! That's about as "smallest" as you can get. But it's not, actually: I used up all of my remaining intelligence points for the day by redirecting the output from ping
into a file, so I could just
(cat output; sleep 1) | zenity --progress --auto-close
and not keep poking at poor gcr.io
until I finally figured this thing out. (The sleep
gave me enough time to see the pop-up when it worked, because zenity
exits when the pipe closes at the end of the input. So, what's in that output
file?
PING gcr.io (172.253.122.82) 56(84) bytes of data.
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=1 ttl=59 time=18.5 ms
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=2 ttl=59 time=21.8 ms
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=3 ttl=59 time=21.4 ms
--- gcr.io ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 18.537/20.572/21.799/1.449 ms
The magic zenity
-killer must be in there somewhere! All that was left (ha, "all"!) was to make my "smallest" example even smaller by deleting pieces of the file until it stopped breaking. Then I'd put back whatever I'd deleted last, and I deleted something else, da capo, ad nauseam, or at least ad minimus
. (Or whatever; I don't speak Latin.) Eventually the file dwindled to
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=1 ttl=59 time=18.5 ms
and I started deleting stuff from the beginning. Eventually I found that it would break regardless of the length of the line, as long as it started with a number that wasn't 0 and had at least 3 digits somewhere within it. Huh. It'd also break if it did start with a 0 and had at least 4 digits within... unless the second digit was also 0! What's more, a period would make it even weirder: none of the digits anywhere after the period would make it break, no matter what they were.
And then, then came the ah-ha! moment. The zenity
documentation says:
Zenity reads data from standard input line by line. If a line is
prefixed with #, the text is updated with the text on that line. If a
line contains only a number, the percentage is updated with that
number.
Wow, really? It can't be that ridiculous, can it?
I found the source for zenity
, downloaded it, extracted it (with tar -xf zenity-3.42.1.tar.xz
), opened progress.c
, and found the function that checks to see if "a line contains only a number." The function is called only if the first character in the line is a number.
108 static float
109 stof(const char* s) {
110 float rez = 0, fact = 1;
111 if (*s == '-') {
112 s++;
113 fact = -1;
114 }
115 for (int point_seen = 0; *s; s++) {
116 if (*s == '.' || *s == ',') {
117 point_seen = 1;
118 continue;
119 }
120 int d = *s - '0';
121 if (d >= 0 && d <= 9) {
122 if (point_seen) fact /= 10.0f;
123 rez = rez * 10.0f + (float)d;
124 }
125 }
126 return rez * fact;
127 }
Do you see it yet? Here, I'll give you a sscce, with comments:
// Clear the "found a decimal point" flag and iterate
// through the input in `s`.
115 for (int point_seen = 0; *s; s++) {
// If the next char is a decimal point (or a comma,
// for Europeans), set the "found it" flag and check
// the next character.
116 if (*s == '.' || *s == ',') {
117 point_seen = 1;
118 continue;
119 }
// Sneaky C trick that converts a numeric character
// to its integer value. Ex: char '1' becomes int 1.
120 int d = *s - '0';
// We only care if it's actually an integer; skip anything else.
121 if (d >= 0 && d <= 9) {
// If we saw a decimal point, we're looking at tenths,
// hundredths, thousandths, etc., so we'll need to adjust
// the final result. (Note from the peanut gallery: this is
// just ridiculous. A progress bar doesn't need to be this
// accurate. Just quit at the first decimal point instead
// of trying to be "clever."
122 if (point_seen) fact /= 10.0f;
// Tack the new digit onto the end of the "rez"ult.
// Ex: if rez = 12.0 and d = 5, this is 12.0 * 10.0 + 5. = 125.
123 rez = rez * 10.0f + (float)d;
124 }
125 }
// We've scanned the entire line, so adjust the result to account
// for the decimal point and return the number.
126 return rez * fact;
Now do you see it?
The author decides "[i]f a line contains only a number" by checking (only!) that the first character is a number. If it is, then it plucks out all the digits (and the first decimal, if there is one), mashes them all together, and returns whatever it found, ignoring anything else it may have seen.
So of course it failed if there were 3 digits and the first wasn't 0, or if there were 4 digits and the first 2 weren't 0... because a 3-digit number is always at least 100, and zenity
will --auto-close
as soon as the progress is 100 or higher.
Spoiler:
The ping
statement generates output that confuses zenity
into thinking the progress has reached 100%, so it closes the dialog.
By the way, congratulations: you found one of the rookiest kinds of rookie mistakes a programmer can make... and it's not your bug! For whatever reason, the author of zenity
decided to roll their own function to convert a line of text to a floating-point number, and it doesn't do at all what the doc says, or what any normal person would expect it to do. (Protip: libraries will do this for you, and they'll actually work most of the time.)
You can score a bunch of karma points if you can figure out how to report the bug, and you'll get a bonus if you submit your report in the form of a fix. ️