The shell re-points a slot before the command runs
◈ 9 cardsUse <, > and >> correctly, and explain why the command they act on is unchanged, unaware, and — for commands like tr — otherwise unusable on a file.
One action, at one moment
Redirection is a single thing that happens at a single moment: before the command runs, the shell opens a file and points one of the three standard descriptors at it. Then it hands over and stays out of the way. The program that starts is byte-for-byte the same program it would have been without the redirection, and nothing inside it can tell the difference.
cmd < file— openfilefor reading, point descriptor 0 at it.cmd > file— createfile, or truncate it to zero length if it exists, and point descriptor 1 at it.cmd >> file— openfilefor appending, point descriptor 1 at it.
Each operator may be written with its descriptor number spelled out: 0< file is < file, and 1> file is > file. Nobody writes the number for 0 or 1 in practice, but you must be able to read it, because the moment stderr enters the picture the numbers stop being optional.
Worked example: sort < students > sorted
Without the redirections, sort would inherit three descriptors all pointing at the terminal. The shell processes the two operators first, so at the instant sort starts, slot 0 points at students, slot 1 points at sorted, and slot 2 still points at the terminal. sort reads what it is handed, sorts it, writes what it produces, and reports any trouble to your screen — and it opened neither file.
Now compare sort students > sorted. Same visible result, different mechanism. students is now an argument: the shell passes the word through untouched, sort opens it itself, and descriptor 0 stays attached to the keyboard the whole time. Two roads, one destination.
Where the two roads stop being equivalent
The difference stops being academic the moment a command takes no filename argument at all. tr is the standard case: it translates or squeezes characters and it reads only from stdin. tr -s ' ' Phones is an error — tr has nowhere to put that word. tr -s ' ' < Phones works, because the shell did the opening and tr never had to be told.
A second case is cat < a > b against cp a b. If b does not exist, the two do the same visible thing. If b does exist they differ, and the difference is about identity: the redirection form opens the existing b, truncates it to zero length, and writes new data into it, so afterwards it is the same inode — same permission bits, same owner, same link count, same hard links still pointing at it. cp is free to replace the destination attributes from the source. cat > preserves the destination file; cp overwrites it. When a file has a carefully set mode, or two hard links pointing at it, that distinction is the whole ball game.
Appending, protection, and the bit bucket
> destroys silently and asks nothing. Bash offers set -o noclobber, which makes > refuse to overwrite an existing file, and >| to override the protection for one command; put the set line in your startup file to make it permanent. (The C shell spells the same idea set noclobber and overrides it with >!.) >> is the other answer: it opens at the end and adds, so nothing already in the file is at risk.
And finally /dev/null, which is used constantly and defined nowhere. It is a real character-special file with a genuinely useful pair of behaviours: every write to it is discarded, and every read from it returns end-of-file immediately. It has no size and never grows. So cmd > /dev/null means run this and throw the output away — what you want when you care only whether the command succeeded, or when you are timing it and do not want to measure your terminal.