1

I have an error function in my POSIX sh shell code. It essentially looks like

error () {
    printf 'utility: ERROR: ' >&2
    printf "$@" >&2
}

The second printf makes it so that I can call the function using e.g.

error 'Something relating to "%s" went wrong!\n' "$thing"
exit 1

and the function would prefix the message with the string utility: ERROR: and arrange with everything being sent to standard error while I'm still able to use a formatting string as I would do with printf.

Obviously, ShellCheck complains about the second printf, saying

SC2059: Don't use variables in the printf format string. Use printf "..%s.." "$foo"

Is it "safe" to do what I do and just ignore this warning (maybe even disabling it with a # shellcheck disable=SC2059 comment in the code)? Note, I always use my error function exactly the way I would otherwise use printf, i.e. with a static single-quoted first argument.

"Is it safe?" here means "Does my function act as a work-alike wrapper (apart from the redirection) around printf in an appropriate way?"

  • 1
    shellcheck can't tell what you might pass in; what about error "$(printf 'Something relating to %s went wrong' "$thing")" -- with some careful quoting inside. Then error() just has one parameter that could be printed with printf "%s\n" "$1". – Jeff Schaller Apr 19 '18 at 10:52
  • @JeffSchaller Thanks. I'd rather avoid having to construct the string in a command substitution before calling the function. The idea with the function is twofold: 1) do error output in a single location in the code (the function) and 2) make it convenient to call it by making it work just like printf. – Kusalananda Apr 19 '18 at 10:59
  • printf -v "$@" >&2? – αғsнιη Apr 19 '18 at 11:11
  • You could construct the argument string inside the function, maybe? printf 'utility: ERROR:%s\n' "$(printf ' %s' "$@")" – steeldriver Apr 19 '18 at 11:41
  • @αғsнιη POSIX printf does not have -v. – Kusalananda Apr 19 '18 at 11:45
2

It's safe in that you do intend here for the format to be passed as argument. But you have to remember when using the function that it is the format. So maybe you should name your function errorf as a reminder.

If you used it as:

error "invalid arg: $arg"

You'd cause problems like for values of $arg like %99999999s

Ideally, you'd want to tell shellcheck to ignore that usage of printf and for it to flag usages of your printf where the first argument contains variables.

A couple of notes:

  • since you're invoking printf twice, that will be at least 2 write() system calls. Unlikely to make any difference whatsoever, and note that some printf implementations like bash's will do several system calls under some circumstance as well. The kind of problem I'm thinking of is the two parts of the error message being intertwined with the output of another command running in parallel.
  • In error "%s\n" error1 error2 the prefix will be output only for the first error. That's fine if that's how you want to do it. You could also do:

    errorf() { printf "utility: ERROR: $@" >&2; }
    

    for the prefix to be prepended to the format, so it be output every time the format is reused (which would also address the multiple write() issue).

    That would however not work if you wanted utility to be a variable though.

    PROG_NAME=${0##*/}
    errorf() { printf >&2 "$PROG_NAME: ERROR: $@"; }
    
    wouldn't be OK as it would fail when $0 contains % or \.

    and

    PROG_NAME=${0##*/}
    errorf() {
    format=$1; shift
    printf >&2 "%s: ERROR: $format" "$PROG_NAME" "$@"
    }
    wouldn't work in the cases where the format is reused (and would mean the user would need to offset by one the %<n>$d directives (not that I would recommend using those as they are not portable)).

    I can't think of any easy work around other than escaping the \ and % by hand in the $PROG_NAME and even that is not easy as with many printf implementations, you'd also need to escape the 0x5c bytes found in other characters than \ (for those charsets like BIG5 or GB18030 that have some).

  • Much appreciated! And thanks! Yes, renaming the function may be a good thing to do, and I might adopt your suggested single-printf suggestion too. Then I'll disable the warning from ShellCheck with a comment (for that specific line). – Kusalananda Apr 19 '18 at 11:43
  • I see your edits, and I will have to take that into account later. Thanks! – Kusalananda Apr 19 '18 at 11:55
  • If I have a variable utility name in my prefix string, it will most likely be inserted by a configure script, which means it won't actually be variable from the script's POV. – Kusalananda Apr 19 '18 at 12:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.