As jthill explained, these are
ansi escape codes, which control how the terminal displays information, in a way somewhat similar to html tags. When inserted in a text stream they aren't displayed directly; instead they tell the terminal how to color and position the text on the screen.
But they're still just ascii characters like all the rest. When this text is viewed in a normal text reader, they become visible, since the reader doesn't know how to interpret them. Again, it's like when you view a raw html file.
sensors apparently doesn't use escape codes in its output, and so when you run it stand-alone all you get is the plain text output. In fact the majority of programs, even when they do use escapes for formatting, are designed to not send them through pipes and redirects by default. That's why your single run and the looping solution works.
If I'm understanding it right, according to the
watch man page, by default it strips out any escape codes from the output of the command it's monitoring. But it obviously must also insert
its own codes into the output, since it wouldn't be able to control and update the display every few seconds without them. So when you pipe this output to a file, you're getting the escapes inserted from
watch along with the raw data from
sensors.