Script gives "if: command not found"

Creating the loop function is straightforward. Calling it is the tricky part.

Here "$@" is the expansion of the arguments passed to loop. It's the same as writing "$1" "$2" "$3" ... for however many arguments there actually are:

loop() {
    while true; do
        "$@"
        sleep 10
    done
}

As you've experienced, variable expansion like this works fine for regular commands, but doesn't handle more complicated cases like if statements, | pipelines, && and || operators, or multi-line commands separated with ; and/or &.

The best way to support all of those features is for the caller to define a function and pass the function name to loop. Functions can use all of these features, and have the advantage of not requiring any complicated quoting or escaping:

hi() {
    if [[ $(ioreg -r -k AppleClamshellState | grep '"AppleClamshellState"' | cut -f2 -d"=") = ' Yes' ]]; then
        ./lockscreen
    fi
}

loop hi

Further reading:

  • BashFAQ/050: I'm trying to put a command in a variable, but the complex cases always fail!