Let's differentiate some terminology:
A command is what you type into your shell. It may be an alias or a shell function, or it may refer to an executable file.
An executable file may be a binary executable (i.e. one that contains machine code directly) or a script. Scripts include bash scripts, sh scripts, perl scripts, awk scripts, sed scripts, python scripts, et. al.
The first two bytes of your script (if it is to be executed directly as an executable file) must be
#!, which is the "magic number" that signals to your kernel to read further bytes until a newline is read, interpret those bytes as a path to a binary executable, possibly with a single argument separated by whitespace (e.g.
#!/bin/awk -f), and execute that binary executable, with the path to the script itself passed in as an argument.
Ultimately, the only thing the kernel can really execute is machine code, i.e., a binary executable. Binary executables such as sh, bash, perl, python, awk, et. al. are called interpreters. They interpret a script and execute its instructions. But they must exist in machine code themselves to be executed.
When a program (binary executable) is actually run by the kernel, it exists as a process. A binary executable is just a file containing instructions. A process is a "running instance of a program"; more specifically it is an abstraction built into the kernel that has associated memory, environment variables, a Process ID (PID), file descriptors which it can use for input and output, and other attributes. You can run an executable file more than once "simultaneously" (not literally simultaneously on a single core machine but because of the way the kernel allocates CPU cycles it will seem simultaneous) and each running instance will be a different process, even though they are all instances of the same program.
0, 1 and 2—standard input, standard output, and standard error—are file descriptors. Truthfully, it is only by convention that they exist at all. You can create a program using C that will launch (run) other programs (execute various binary executables) without giving them these file descriptors at all. However, since the standard programs are all written assuming the availability of file descriptors 0, 1 and 2, you will probably get nothing but errors (in most cases) and the programs will not work correctly.
To really understand this fully, you should understand how a process comes into being. This is a little bit like the miracle of birth. ;) All processes have to be started by another process. Without worrying about how the first process gets started when you boot up the system, the process with PID 1 is called "init" and it starts the other basic processes your operating system needs in order to function.
How a process starts another process is with two basic steps: fork and exec. Both of these are system calls, which is to say they are actions/requests that the process sends to the kernel which only the kernel can actually fulfill.
"Fork" means (in a nutshell), "kernel, please make a copy of me." ("Me" being a running process.) The kernel makes a complete copy of the process—its file descriptors, memory, state of execution (where it is at in following the instructions which comprise the program of which it is an instance), environment variables and so forth. So it's a "clone" of the process. Now how can you tell the original from the copy? By only one thing: the return status of the
fork system call. The child process gets "0" (success) and the parent process gets the PID of the newly created child process. Thus by inspecting this return status, each process can figure out what it should do now (because remember, they are following the same set of instructions!).
"Exec" is really "execve()". In a nutshell, it asks the kernel, "kernel, please replace me (I'm a process) with an instance of the program specified in ______ file." And the programmer also specifies the arguments that the new process will have, and the environment (array of environment variables) that it will have.
So when you type a command into the shell, what really happens (most of the time, ignoring special cases such as shell builtin commands like
cd) is that your shell (which is a running process) forks, and then execs the command you specified.
If you've done output or input redirection like
/bin/echo hello > /dev/null, then the forked child process before
execing echo will adjust its file descriptors accordingly, so that file descriptor 1 (in this example) is tied to
/dev/null instead of your terminal or wherever it was before.
So yes, any running instance of the executable file
/bin/bash will expect to have available a file descriptor 0 from which it can read input, a file descriptor 1 to which it can write output, and a file descriptor 2 to which it can read and write error messages and similar in/output.