Excellent explanation! However, I would disagree both that there isn't an easy fix and that each of the decisions are sensible.<p>If the syntax to temporarily set variables was different, say using : instead of =, or requiring some delimiter between assignments and operations, or maybe enclosing the statement in braces (something like: {foo=bar; echo $foo}...) to delimit temporary assignments would have been reasonable alternatives, perhaps even more readable than this. Or having the sntax disallow `foo=`, requiring, say, `foo=null` or `foo=''`, which are both more explicit, or even `unset foo`. These are more sensible individual choices, and that's partly because they're more explicit and partly because they avoid a (now) common error. I'd argue good language design needs to think slightly bigger picture than "individually not catastrophic" choices. Sh is littered with a myriad of small bad choices, which add up to a language that's hard to code or script without bugs. Nowadays with shellcheck it's easier, but it shouldn't be necessary. The consensus is: it's a scripting language, it shouldn't be used for complex stuff. But if it was a slightly better scripting language, it _could_ be used more safely for larger or more complex scripts.<p>The truth is that it's an ancient language, its (bad) syntax is set in stone, and there's nothing we can do about it except slowly move to alternatives. Sad that there's nothing established to take its place (Perl is read-only, python is not good enough as unix glue, everything else is too obscure).<p>I know how and love to write in bash. But oh god was it painful to learn
For what it’s worth, the explanation of the error was obvious to me after seeing the code for a few seconds. So maybe my point is… don’t worry if stuff like that in shell programming seems obscure to you; it becomes much less obscure with practice (and along the way you do learn genuine lessons about unix-like operating systems, not just shell programming details).
This does not seem obscure to me at all. Having learned about C from reading djb's programs, I write programs that read their arguments from environmental variables. (See djbdns for example.) When I run one of these programs it's common to set variables on the command line, e.g.,<p><pre><code> echo https://example.com|Connection=keep-alive yy025|nc -vvn 127.127 80
</code></pre>
yy025 sets the HTTP Connection header according to the value of the corresponding environmental variable. If I want to send a POST request I set the variable httpMethod to POST. The address 127.0.0.127 is a TLS forward proxy. Original netcat allows one to type 127.127 without the zeros.<p>Using djb's envdir utility, one can put a selection of different HTTP headers into a directory so that there's no need to type them on the command line.<p><pre><code> cd /path/to/dir
echo close > connection
echo aplication/x-www-form-urlencoded > content-type
echo id=123\;key=xyz > cookie
echo 19 > content-length
echo POST > httpMethod
echo /api/whatever > path
echo "{ \"key\": \"value\" }" > post-data
cd
echo https://example.com \
|if cd /path/to/dir;envdir . yy025;then
cat post-data;fi \
|nc -vvn 127.127 80
</code></pre>
It's strange to me to see this author use printenv instead of set (a built-in) to display Bourne shell environmental variables. As someone who uses Bourne shell every day and on a variety of computers, I never use printenv. On the smaller systems I make, I do not include printenv in the userland.<p>The use of fgrep to match a single word in all caps is also strange.<p>Maybe I am just missing some UNIX wisdom here. If so, please disregard this comment.<p>From the article<p><pre><code> $ export FRED=value
$ printenv | fgrep FRED
FRED=value
$ FRED=
$ printenv | fgrep FRED
FRED=
$ unset FRED
$ printenv | fgrep FRED
$ # ie, no output from printenv
</code></pre>
Without using printenv or grep -F (in shell script /bin/fgrep)<p><pre><code> $ export FRED=value
$ set|grep FRED
FRED='value'
FRED=
$ set|grep FRED
FRED=''
$ unset FRED
$ set|grep FRED
$ # ie, no output from set</code></pre>
Can I suggest a simple fix that doesn't involve changing behavior at all but probably improves the lives of the many users who hit this?<p>Just add an additional line to the error message for lines that look like this pattern:<p><pre><code> sh: 107: command not found
Did you mean "AVAR=$(... | wc -l)"?</code></pre>