0

I have a bash script repeat loop that checks for a variable in a folder of file names and then echoes an error message for each file it finds, after which it echoes an ongoing file count for the ones it successfully processes.

Here is the relevant code:

if [[ $myVar = 1 ]]; then
    echo "ERROR: $myFilename"
    ((count++))
    echo -en "Total Files: $count \r"
fi

The objective is to insert a blank line between the last error message it displays and the total file count, like this:

ERROR: testfile1
ERROR: testfile2
ERROR: testfile3

Total File Count: 3

The problem I am having is, without adding an echo "" after the error message, it looks like this:

if [[ $myVar = 1 ]]; then
    echo "ERROR: $myFilename"
    echo""
    ((count++))
    echo -en "Total Files: $count \r"
fi

ERROR: testfile1
ERROR: testfile2
ERROR: testfile3
Total File Count: 3

But if I add an echo "" after the error message, it adds the blank like to each cycle of error checking so it looks like this:

if [[ $myVar = 1 ]]; then
    echo "ERROR: $myFilename"
    echo""
    ((count++))
    echo -en "Total Files: $count \r"

ERROR: testfile1

ERROR: testfile2

ERROR: testfile3

Total File Count: 3

My question is, how can I add that blank line to the code (either in this section or after) so that the [echo ""] is only applied at the end? Is there a special condition in Bash that would allow me to run a command only once within the repeat loop?

I need the counter to remain inside the loop so it increments the number after each cycle, but I also want to insert a blank line between them.

6
  • $'\r' only does what you want when the cursor is on the same line you want to overwrite. When you have other lines printed, you're overwriting the line the cursor is now on, not the line it was on when you last printed your message. Commented Apr 25, 2021 at 20:42
  • Mind, there are cursor control sequences you can use to send the cursor upwards, instead of just to the left. Commented Apr 25, 2021 at 20:43
  • Is your program really running some extra/external/separate script that prints the ERROR: $myFilename message, such that you want to suppress that message? That's an easy enough problem. Commented Apr 25, 2021 at 20:44
  • (...and btw, this is kind of a digression, but echo -en is really best avoided; printf '\r%s' "message here" is much more reliable, in that it'll work the same way across different bash configurations [and non-bash shells], different operating systems, etc. more reliably) Commented Apr 25, 2021 at 20:45
  • I'm kind of new to this so forgive the rookie errors, The \r is there so that the "Total File Count: #" keeps overwriting itself, thus creating the illusion of a counter incrementing upwards for each file processed. When you're doing thousands of files, this is very useful. The code first scans for valid filenames, reports back on those that are problems, and then processes the remaining files. I'm just trying to insert a blank line between the last error line and the file count line. Commented Apr 25, 2021 at 21:03

2 Answers 2

1

If you want your error messages to be above your running counter, use tput cuu 1 to move the cursor upwards before you print your error messages.

#!/usr/bin/env bash
count=0

printf '\n'
for ((i=0; i<10; i++)); do
    sleep 0.2                                # delay for demonstration purposes
    if (( i % 2 == 0 )); then                # if we have an error:
      tput cuu 1                             # move the cursor up 1 line
      printf '\r%s\n' "ERROR: processing file $i" # print our error message
      tput el                                # blank the following line
      printf '\n'                            # make new blank line for counter
    fi
    ((count++))
    printf '\r%s' "Total files: $count"      # now update our counter message
done

...properly leaves output as:

ERROR: processing file 0
ERROR: processing file 2
ERROR: processing file 4
ERROR: processing file 6
ERROR: processing file 8

Total files: 10
Sign up to request clarification or add additional context in comments.

5 Comments

So, if I follow, because you can't avoid inserting a blank line after each error iteration when it's inside a loop, you allow it to be inserted and then immediately remove it by moving the cursor upward each time, until the last message when there is nothing to remove, which leaves that blank line intact?
I just tried it and it works, though the console looks pretty jerky as it is happening. Is there any trick to make it look smoother?
There are some tricks that exist, though relying on them gets into the space of relying on terminal-specific features. I'm a little curious if you're running this over a serial line or ssh connection -- when I was testing the above locally, the updates all went too quickly to see what was going on as they were drawn.
@macuseronline, ...for details when I refer to "tricks that exist" -- read through the terminfo man page to see the features tput can access. For example, csr gives you a settable scroll region, so text can scroll in part of the terminal without changing other lines. Also, notice the ability to set the terminal's status line (often, the window title for the terminal window or tab) -- you could, for example, keep the running total in the window title, and show only errors in the other content. But I'd think hard about whether the complexity is worth it before pursuing those approaches.
@macuseronline, ...if what's going on is that the error messages are written out slowly by the code you're invoking in the loop, then it's an available approach to buffer them and emit them in a single syscall (still inside that same loop). But that's speculation, and I'd really need to see the behavior that isn't looking great to speak to how to improve it; when I run this myself in an xterm-256color window on Linux (KDE's Konsole), it looks great.
0

You didn't demonstrate how your actual loop is. I can only guess how you try to loop.

Solution A: Put the total count print out of loop

for myFilename in myFilenameList; do 
  if [[ $myVar = 1 ]]; then
    echo "ERROR: $myFilename"
    ((count++))
  fi
done

echo
echo -en "Total Files: $count"

Solution B: Keep total count print within loop, and use a number to determine end of loop

totalLength=${#myFilenameList[@]}

for myFilename in myFilenameList; do 
  if [[ $myVar = 1 ]]; then
    echo "ERROR: $myFilename"
    ((count++))
  fi

  ((totalCount++))
  if [ $totalCount -eq $totalLength ]; then
    echo
    echo -en "Total Files: $count"
  if 
done

Note they are rough ideas rather than working codes.

6 Comments

None of this addresses the OP's intent to use a carriage return to overwrite the same lines in-place.
Yeah, the code is kind of mangled to make it short, sorry about that. The problem is I am in a catch-22. If I take the count out of the loop it won't give me an incremental count of the files processed, and I don't have a pre-set number of files it will process to be able to set that variable in advance. The only thing I do have (that I didn't include in the example) is a "continue" statement at the end that then takes the code to the next step of processing the files. I wish there was some way to only run a command at the end of the loop before it continues processing.
If there was a way to tell that echo "" line to run only after the loop finishes and before the code gets handed off to the next step.
@macuseronline, By that echo "" line, do you mean the one with the error message? That's easy enough -- I'd do it by just storing the error in a variable, and printing the variable's contents after your loop exits.
The errors are printed on an ongoing basis as they occur, after which I want a blank line, after which the Total File Count increments as the code processes the remaining files, all of which is within the loop itself. So I can't print the errors after the fact.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.