SC2094 triggers for foo $(cat bar) > bar
Hi,
ShellCheck complains about the implementation of my __inc function:
__inc() {
printf '%d' $(($(< "$1") + 1)) > "$1"
}
As far as I understand, there is no race condition here as the read / write don't occur within the same pipe.
I would guess the $(< "$1") would first read in the value as a whole, right?
I wouldn't guarantee it. Parsing and execution are separate phases, and some expansions are done earlier than others.
And, in the shell grammar, a "pipeline" is one or more commands connected by pipes. So yes the read and overwrite redirections are in the same pipeline.
Yeah, barring specific spec language indicating that the command substitution must be performed first this seems unnecessarily risky to me. (I took a quick look at the spec but didn't see anything obvious that indicated a required order of operations here.)
It might always work, it might be required to work even I just don't see that anywhere at the moment.
So I guess it will write the output to a temporary file and then mv it as suggested. Thanks for your answers!
You're right, this is a false positive. Here's POSIX:
.2. The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.
.3. Redirections shall be performed as described in Redirection.
ShellCheck does not account for this, but it should.
In a POSIX-compliant script this is indeed a false positive, so I withdraw my previous "wouldn't guarantee it".
However it's brittle, since a seemingly innocuous change could subvert the necessary preconditions.
For example, even with POSIX, this will break:
{ printf '%d\n' $(( $(< "$1") + 1 )) ; } >"$1"
I would simply read and write as separate statements:
__inc() {
local n
n=$(< "$1") &&
printf '%d\n' $((n+1)) >"$1"
}
Beyond POSIX there are weird combinations where expansions and redirections are necessarily intermingled, but I haven't been able to construct a case where a expansion that's used as input isn't completed before one that's used as an output, unless the ordering is controlled by using a compound command, or the input is process through a pipe such as <(...) or a regular multi-component pipeline.