Date:        Wed, 9 Sep 2026 21:38:22 -0400 (EDT)
    From:        Mouse <[email protected]>
    Message-ID:  <[email protected]>

  | Perhaps not in some technical sense.

Which is what needs to be considered when evaluating how it is interpreted
(and for example, why there is no need to quote its arg, unless the arg
contains operators or white space, where there is a need to quote almost
every other expansion on a normal command line, including var=value when
that appears as an arg to a command like export, or local, or dd!).

  | But it is a command in the sense
  | of "something that can be typed interactively, or otherwise given where
  | a command-line is expected, to do something useful".

Sure, if you're using it imprecisely, with no particular meaning implied
beyond "something happens".   But you need to recognise the difference
between the two cases, when discussing the technical details, precision
is important.

  | It sounds to me as though you're drawing a distinction between variable
  | assignments and "command"s in the sense of...I'm not sure what,

It is related to how they are parsed and interpreted,

If for example "$1" is '*' then

        foo=$1

is eventually expanded, and does the same thing as

        foo=*

which is exactly the same as

        foo='*'

That is, no attempt is ever made to filename expand the '*', orto
field split the results of the expansion, or if "$1" had included
white space (or any IFS characters) in its value - and regardless of
whether or not any files exist with names foo=abc foo=123 ...

On the other hand, should we write it as

        \foo=$1

then the 'f' is quoted, that prevents it from being a var-assign,
(that's one of the rules) and so (being the first, and here, only,
word in the command, eventually supplies the command name).   But before
we allocate it as being the command name, all the arg words (which this is,
before the first one is taken and used as the command name) get expanded.
So that would turn into

        \foo=*

field splitting comes next, but unless IFS happened to contain '*' as one
of the chars in its value, that changes nothing, after which filename
expansion and as the * isn't quoted, filename expansion would then happen,
resulting in

        \foo=123 \foo=abc ...

after which quote removal happens, resulting in

        foo=123 foo=abc ...

and then we we take the first resulting word as the command name, and
run the "foo=123" command with its args being the remaining foo=xxx file
names (the assumption here is that the foo=123 name is the first when the
results of the expansion are sorted).   For this, "foo=123" can be a
file-system command, or built-in - that cannot be determined without
assigning more reasonable names than "foo" to everything, and knowing
exactly what is in the directory, and what built-in commands the shell
in question has implemented.   However, what it cannot be is a var-assign,
that possibility was excluded as soon as the quoted 'f' was originally
discovered.

And yes, this is all technical - it is the rules of the shell programming
language, just as all other programming languages (and most natural ones
as well), have rules, and correct interpretation, and generation, depends
upon an understanding of the rules.

  | maybe conceptually running a program?

No, that's not it at all, it all relates to the parsing and evaluation
rules (what gets expanded, how, and when) and the meaning attributed to
the construction.

Eg: if we have

        E=
then
        $E
is a command.   Nothing is run, conceptually or otherwise, when $E
is expanded, nothing remains at all, but that's still a command.
And yes, once again, all this is technical.   (The exit status of
that command is even defined to be 0.)

  | I'm having trouble defining "command"
  | in a way that makes "cd" a command (one of the few commands that
  | _can't_ be implemented as a separate executable) but "foo=bar" not,
  | except by arbitrary fiat.

There are actually quite a few commands, not just cd, that can't be
implemented as a separate executable, ulimit, umask, break, return,
export, jobs, wait, exit ... they're all commands, and none of them would
work as a separate executable (not work as intended, you can make
separate executables for all of them, they all do their thing, and
finish, without any effects that might have been hoped for - ones
like ulimit and umask even work completely, when only used to display the
current settings, rather than change them).

And yes, to an extent, it is arbitrary fiat, just as the rules of
precedence of the operators in C are for example (and that the > and &
operator precedences are backwards wrt each other from what they ought
to be) - and that in C, unlike many other languages, assignment is an
expression operator, rather than a statement type.

Everything has rules, the rules alter the meanings, capabilities, and
usage of the various constructs.  With var-assigns, the sh definition
allows for things like

        V1=foo V2=bar V3= cmd [arg....]

which simply couldn't work sanely if V1=foo was treated as a command,
rather than just a modifier of the command (like redirects also are).

The degenerate case

        V1=foo V2=bar V3=

with no more words to be the command & its args, is just that, a
degenerate case of the same operation, modifiers applied to nothing,
but for which the rules have assigned an interpretation.

kre

Reply via email to