in reply to Printing to stdout an array of strings and scalar references

"I am looking for any suggestions ... that might lead to a more elegant solution."

One of the benefits of using lexical variables is that you can control their scope. Declaring all your variables at the start of runtime, and giving them file-scope, defeats this. You should use the smallest scope possible. You should generally also pass variables to your subroutines: there are exceptions to this such as in the code below; although, do note the anonymous block limiting the scope of the variables not passed in the function call (see also state for an alternative way to handle this).

Using an arrayref, as you've shown, is not something I would do. Using one of the string formatting functions (sprintf in the code below) would be a better choice. Adding a newline to each line of output, actually creates something more like a log than a progress indicator. Starting each output line with a carriage return (\r) is not doing what I think you hoped it would; the usual way to handle this is to use backspaces (\b) and then write the new indicator lines, effectively overwriting the previous indicator line.

Also note that writing lines without a terminal newline can cause buffering problems. I've set the special variable "$|" to overcome this; and localised that change so that it doesn't affect other I/O.

Here's example code implementing those techniques.

#!/usr/bin/env perl use strict; use warnings; my @files = map { "file_$_" } 5 .. 14; for (0 .. $#files) { show_progress(\@files, $_); # Process $files->[$_] here. For testing: sleep 1; } show_done(); { my ($prog_fmt, $last_msg_len); BEGIN { $prog_fmt = 'Processing "%s" (%d of %d) ...'; $last_msg_len = 0; } sub show_progress { my ($files, $index) = @_; local $| = 1; my $clear = "\b" x $last_msg_len; my $msg = sprintf $prog_fmt, $files->[$index], 1+$index, 0+@$f +iles; $last_msg_len = length $msg; print $clear, $msg; return; } } sub show_done { print "\nProcessing completed.\n"; return; }

The first line of output looks like this:

Processing "file_5" (1 of 10) ...

That line is continually overwritten. The final output looks like this:

Processing "file_14" (10 of 10) ... Processing completed.

— Ken

Replies are listed 'Best First'.
Re^2: Printing to stdout an array of strings and scalar references
by Anonymous Monk on Oct 25, 2017 at 02:39 UTC

    Hi Ken

    Thanks for your help,
    To clarify I generally do pass variables into my subroutines, the use of global's in the sample code was for demonstration purposes, as was the newline feed at the end of the output line, it was purely intended to demonstrate the output without having to introduce delays.
    I have never really understood the uses of anonymous blocks of code, your explanation and code sample has helped to clarify why I might need to use that method in the future, and I will also check out state as you recommend.
    I have traditionally turned on Autoflush globally when I need it (as indeed I do for the purposes of the progress indication), I notice you are scoping it as a local to the subroutine, is there an advantage to this method? or alternatively a disadvantage to using autoflush globally?
    sprintf is also something I have not used much, but I can see how your code could be adapted to achieve the variable output I require without resorting to references within the array so I will also put more effort into understanding how it works.

    Thankyou for your assistance, it has been educational :-)

      "Thanks for your help"

      You're very welcome.

      "I have traditionally turned on Autoflush globally when I need it ..., I notice you are scoping it as a local to the subroutine, is there an advantage to this method? or alternatively a disadvantage to using autoflush globally?"

      Perl has a number of special variables (see perlvar). Some of these are set in a certain context ($a - sort; $1 - regex; $@ - eval; and so on). Others have default values which are usually fine and don't needed changing; code is written expecting these default values; changing them globally can mean that some code does not function as intended or expected. Changing them with local, in a limited scope, means the change does not affect other code:

      $ perl -E 'say $|; { local $| = 1; say $| } say $|' 0 1 0

      If you look in "Variables related to filehandles", you'll see (a few paragraphs down):

      "You should be very careful when modifying the default values of most special variables described in this document. In most cases you want to localize these variables before changing them, since if you don't, the change may affect other modules which rely on the default values of the special variables that you have changed. ..."

      That continues on with good and bad examples. A discussion of scope follows that:

      "Usually when a variable is localized you want to make sure that this change affects the shortest scope possible. ..."

      There are more good and bad examples; one showing use of an anonymous block.

      Further related information can be found in perlsub; in particular, the sections "Temporary Values via local()" (especially the "Localization of special variables" subsection), and "When to Still Use local()".

      — Ken