-1

I face the following problem, which looks like a bug (or maybe am I the bug):

I have a loop:

#!/bin/bash
#
TARGET=/sd/REMOTE_BACKUP
LOGDIR=/sd
DEBUG=:

# Init daily information (reset on each run and at midnight)
TODAY=$(date +%Y-%m-%d)
TODAYCOUNT=0
TODAYDELTA=0

RECOMPUTESTORAGE() {
        echo 123 456
}

        inotifywait -m -e close_write -e moved_to -e delete --recursive --format "%w %f" $TARGET/ \
        | while read DIRNAME FILENAME EXTRA
                do
                        tput el
                        $DEBUG "DIRNAME=$DIRNAME        FILENAME=$FILENAME"

                        # 3.6 - Exit request
                        [[ -f $TARGET/.exit ]] && \
                                echo "${BOLD}Exit${SGR0} request received..." && \
                                rm -v $TARGET/.exit && \
                                exit

                        # 3.7 - Recompute storage request
                        [[ -f $TARGET/.recompute ]] && \
                                read FULL USED <<<$( RECOMPUTESTORAGE ) && \
                                DELTA=$USED && \
                                rm -v $TARGET/.recompute && \
                                continue


                        if [ "${FILENAME}" == "" ]; then
                                # Simply skip, but take the opportunity to refresh
                                echo "${FILENAME} REMoVED"
                        else
                                echo -e "\nUnencrypted file $FILENAME *NOT* externalized"
                        fi

                        echo -n -e "\rWaiting event...${EL}\r"

                done \
        | tee ${LOGDIR}$(basename $0).log

But on execution time, after echoing "found non-empty $TARGET", the execution continues with the echo "proceed with the loop" instead of branching (ugly word I know) to the next read.

Based on Bash's manual the "continue" statement should jump to the next iteration, ignoring the rest of the loop body.

Can you explain why this does not correctly occur?

Thank you

Ouput:

 $ sudo bash /tmp/b                                                                                      
Setting up watches.  Beware: since -r was given, this may take a while!                                                            
Watches established.
removed '/sd/REMOTE_BACKUP/.recompute'

Unencrypted file .recompute *NOT* externalized
Waiting event...
6
  • It's possible && continue just doesn't work. If you put your conditions in one if statement and use ; then continue that should work. Commented May 9 at 7:37
  • 2
    What happens if you turn $DEBUG into echo? Commented May 9 at 7:39
  • 2
    @PeterPointer: && continue works correctly in a simple while loop (easy to test). Commented May 9 at 7:40
  • 1
    Note that $DELTA is assigned a value in a right hand side of a pipe, i.e. in a subshell, i.e. it won't retain the value outside the loop. Commented May 9 at 7:51
  • 3
    Where are those "found non-empty $TARGET" and "proceed with the loop" echoes you mentioned? They are not in the code you show. The rest of your question makes sense though. Commented May 9 at 10:32

2 Answers 2

1

Modified version of OP's script:

$ cat b
#!/bin/bash

TARGET=.

RECOMPUTESTORAGE() { echo 123 456; }

inotifywait -m -e close_write -e moved_to -e delete --recursive --format "%w %f" "${TARGET}" |
while read DIRNAME FILENAME EXTRA
do
    echo "############## $(date)"
    typeset -p DIRNAME FILENAME EXTRA

    [[ -f "${TARGET}"/.recompute ]]          && \
    read FULL USED <<<$( RECOMPUTESTORAGE )  && \
    DELTA="${USED}"                          && \
    rm -v "${TARGET}"/.recompute             && \
    continue

    if [ "${FILENAME}" == "" ]; then
        # Simply skip, but take the opportunity to refresh
        echo "${FILENAME} REMoVED"
    else
        echo -e "\nUnencrypted file ${FILENAME} *NOT* externalized"
    fi
done

In 1st console I run:

$ ./b
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
                             # blinking cursor

In 2nd console I run:

$ touch .recompute

Back in the 1st console the following is generated:


############## Fri May  9 08:13:13 CDT 2025
declare -- DIRNAME="./"
declare -- FILENAME=".recompute"
declare -- EXTRA=""
removed './.recompute'
############## Fri May  9 08:13:13 CDT 2025
declare -- DIRNAME="./"
declare -- FILENAME=".recompute"
declare -- EXTRA=""

Unencrypted file .recompute *NOT* externalized
                             # blinking cursor

From this output we see 2 passes through the loop (ie, we have 2 sets of ############## Fri May 9 08:13:13 CDT 2025):

  • 1st pass through the loop is due to the manual touch .recompute.
  • 2nd pass through the loop is due to the script's rm -v "${TARGET}"/.recompute.
  • Net result: the continue is working as expected

It's not clear from OP's description if this 2nd pass is expected. With the .recompute file residing in the watched directory the 2nd pass is going to happen regardless of what the OP wants to happen. If OP does not want the 2nd pass to do anything then some additional logic could be added (eg, [[ "${FILENAME}" = .recompute && ! -f .recompute ]] && continue?).


Side not ... if there's a chance any of the chained commands could error out or fail, but you want all of the commands to run regardless of the previous command, consider the following replacement:

[[ -f "${TARGET}"/.recompute ]] && {
    read FULL USED <<<$( RECOMPUTESTORAGE )
    DELTA="${USED}"
    rm -v "${TARGET}"/.recompute
    continue
}
Sign up to request clarification or add additional context in comments.

Comments

1

Based on Bash's manual the "continue" statement should jump to the next iteration, ignoring the rest of the loop body.

Can you explain why this does not correctly occur?

The continue works just fine. The output you present comes from two different iterations of the loop:

  1. Any event is reported on directory /sd/REMOTE_BACKUP -- possibly, but not necessarily, a close_write or moved_to event for file .recompute. When the script handles this, the .recompute file is present. After acting on this, your script deletes .recompute then continues to the next iteration.

  2. Meanwhile, the deletion of /sd/REMOTE_BACKUP/.recompute has caused a delete event, which inotifywait promptly reports into a new iteration of the loop. But the file no longer exists, so the [[ -f $TARGET/.recompute ]] test fails. Control bypasses the continue to reach the if statement, where the additional output you show is emitted.

You need at least these things:

  • provide appropriate special handling for events associated with the .recompute file, regardless of whether it actually exists. Maybe also for the .exit file.

  • take care to avoid losing events fired for other files if received while a .recompute file exists in the target directory.

Note also that because it's part of a pipeline, the while command will run in a subshell, making the assignment to DELTA ineffective for the purposes of anything outside the loop. In Bash (but not in some other shells), you can solve this by redirecting from process substitutions instead:

Something like this should do it:

DEBUG=echo
# DEBUG=:

while read DIRNAME FILENAME EXTRA; do
  tput el
  $DEBUG "DIRNAME=$DIRNAME        FILENAME=$FILENAME"

  # 3.6 - Exit request
  [[ -f "$TARGET/.exit" ]] && 
    echo "${BOLD}Exit${SGR0} request received..." &&
    rm -v "$TARGET/.exit" &&
    exit

  # 3.7 - Recompute storage request
  [[ -f "$TARGET/.recompute" ]] &&
    read FULL USED <<<$( RECOMPUTESTORAGE ) &&
    DELTA="$USED" &&
    rm -v "$TARGET/.recompute"
    # no continue here

  case "$FILENAME" in
    .exit|.recompute) continue;;
    "")
      # Simply skip, but take the opportunity to refresh
      echo "${DIRNAME} modified or deleted"
      ;;
    *)
      echo -e "\nUnencrypted file $FILENAME *NOT* externalized"
      ;;
  esac

  echo -n -e "\rWaiting event...${EL}\r"
done \
  <(inotifywait -m -e close_write -e moved_to -e delete --recursive --format "%w %f" "$TARGET/") \
  >(tee "${LOGDIR}$(basename "$0").log")

Even then, however, do note that the read is going to be problematic for you if ever inotifywait reports an event where the directory or file name contains whitespace.

Comments

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.