Why does the error message for two colons as a command (::) in bash have three colons, but a single colon give no output?

The last colon is just part of the default "not found" message:

$ x
x: command not found
$ ::
::: command not found

The reason a single colon produces nothing is that : is a valid command - although it does nothing (except return TRUE). From the SHELL BUILTIN COMMANDS section of man bash:

   : [arguments]
          No effect; the command does nothing beyond  expanding  arguments
          and  performing any specified redirections.  A zero exit code is
          returned.

You will sometimes see it in constructions like

while :
do
  something
done

See for example What purpose does the colon builtin serve?


The : shell built-in vs non-existent ::

The : shell built-in command exists (note the difference between external and built-in commands) which does nothing; it just returns success, just like the true command. The : built-in is standard and defined by the POSIX standard, where it's also known as the "null utility". It is frequently used for testing or for running infinite loops as in while : ; do ...;done

bash-4.3$ type :
: is a shell builtin

However, :: - two colon characters together - are interpreted as one "word" to the shell, and it assumes to be a command the user entered. The shell will go through the process of checking built-ins, then any directory in the PATH variable for existence of that command. But there is neither a built-in :: nor external command ::. Therefore, that produces an error.

Well, what is a typical format for an error?

<shell>: <command user typed>: error message

Thus, what you see isn't 3 colons but what you typed pasted into the standard error format.

Note also, that : can take command-line arguments, i.e. it is legal to do:

: :

In this case, the shell will consider that as two "words", one of which is a command and the other a positional parameter. That will also produce no error! (See also the historical note (later in this answer) about the use the of : with positional parameters.)


In shells other than bash

Note that formatting can vary between different shells as well. For bash, ksh, and mksh the behavior is consistent. For instance, Ubuntu's default /bin/sh shell (which is actually /bin/dash):

$ dash
$ ::
dash: 1: ::: not found

where 1 is the command number (equivalent to the line number in a script).

csh by contrast produces no error message at all:

$ csh
% ::
%

In fact, if you run strace -o csh.trace csh -c ::, the trace output in csh.trace file reveals that csh exits with exit status 0 (no errors). But tcsh does output the error (without outputting its name, though):

$ tcsh
localhost:~> ::
::: Command not found.

Error messages

In general, the first item in the error message should be the executing process or function (your shell tries to execute ::, hence the error message comes from the shell). For instance, here the executing process is stat:

$ stat noexist
stat: cannot stat 'noexist': No such file or directory

In fact, POSIX defines the perror() function, which according to the documentation takes a string argument, then outputs error message after colon, and then newline. Quote:

The perror() function shall map the error number accessed through the symbol errno to a language-dependent error message, which shall be written to the standard error stream as follows:

  • First (if s is not a null pointer and the character pointed to by s is not the null byte), the string pointed to by s followed by a colon and a <space>.

  • Then an error message string followed by a <newline>.

And the string argument to perror() technically could be anything, but of course for clarity it's typically the function name or argv[0].

By contrast, GNU has its own set of functions and variables for error handling, which a programmer can use with fprintf() to stderr stream. As one of the examples on the linked page shows, something like this could be done:

  fprintf (stderr, "%s: Couldn't open file %s; %s\n",
           program_invocation_short_name, name, strerror (errno));

Historical note

In old Unix and Thompson shell, : was used with goto statement (which according to user named Perderabo on this thread wasn't a shell built-in). Quote from the manual:

The entire command file is searched for a line beginning with a : as the first non-blank character, followed by one or more blanks, and then the label. If such a line is found, goto repositions the command-file offset to the line after the label and exits. This causes the shell to transfer to the labelled line.

So you could do something like this to make an infinite loop script:

: repeat
echo "Hello World"
goto repeat

Try any other non-existent command and you'll see that the : serves its normal purpose in English:

$ ---
---: command not found