The line count does not change — the format of the output does. When you run wc -l f2, the command receives the filename as an argument, reads that file itself, and reports both the count and the name. When you redirect the file into standard input with wc -l < f2, the command has no argument to name. It counts the lines of an anonymous byte stream and prints only the number, because it has literally no idea where that data came from.
Two ways of feeding wc
Like most classic Unix filters, wc can obtain data in two distinct ways, and it reports differently depending on which one you use:
- Filename argument:
wc -l f2— the program opensf2itself. Because it knows the name, it includes it in the output:559 f2. If you pass several files, it even adds atotalline summarizing all of them. - Standard input:
wc -l < f2orcat f2 | wc -l— the shell opens the file and attaches it to the program's stdin, or another process pipes bytes in. The program sees a stream with no associated name, so the output is just559.
When you give wc no argument at all, it defaults to reading standard input. That is why both redirected variants in the original example produce the bare number: the shell performs the redirection before wc even starts, and wc cannot tell that the stream on stdin happens to be the contents of f2.
Why the shell does not tell wc the name
Redirection is performed entirely by the shell. When it sees < f2, it opens the file and duplicates its file descriptor onto file descriptor 0 (standard input) of the child process. What the child inherits is an open file descriptor — not a path, not an argument, not metadata it can print. Multiple redirects of the same stream would only complicate matters; the descriptor is simply "some readable source." This design is deliberate: it is what lets every filter work uniformly on files, pipes, device nodes, and process substitutions without caring about the origin.
There is one caveat worth knowing: if stdin is a regular file, a determined program could recover a name with /proc/self/fd/0 on Linux, but no standard tool does this, and it would not work for pipes or sockets anyway. For practical purposes, treat redirected input as nameless.
The mirror case: detecting the output side
Input redirection changes what wc knows about its source. A related but different phenomenon happens on the output side: some programs check whether their stdout is a terminal and adjust their behavior accordingly. The classic example is ls --color=auto, which emits color escape codes only when standard output is connected to a terminal — so colors show up interactively but never pollute files or pipes.
Programs do this with the isatty() call. A minimal illustration:
#include <stdio.h>
#include <unistd.h>
int main() {
if (isatty(STDOUT_FILENO)) {
fputs("output not redirected\n", stdout);
} else {
fputs("output was redirected\n", stdout);
}
return 0;
}
Run directly, it reports a terminal; run with > file, it detects the redirect. Tools that use this trick include ls, grep (color output), and git (paging and coloring). Note the direction of the check: wc's changed output stems from a missing input name, not from terminal detection — but in day-to-day shell work the two look confusingly similar, because both cause output to differ between an interactive run and a redirected run.
Getting just the number, reliably
Once you understand the rule, the cleanest ways to get only the line count are:
wc -l < f2 # bare count, reads stdin
cat f2 | wc -l # same, via a pipe
awk 'END{print NR}' f2 # alternative, always nameless output
Avoid fragile tricks like wc -l f2 | awk '{print $1}' unless you need them — the redirected form is simpler and does not depend on column layout.
Preventing confusion in scripts
- Remember that anything you pipe or redirect strips the filename from
wc,du, and similar reporting tools. - If you need both the count and the name, keep the argument form and strip columns, or capture the count into a variable first.
- When a command behaves differently interactively versus in a script or pipeline, check whether it inspects
isatty— flags like--color=alwaysorgit --no-pagerexist precisely to override that detection. command < fileandcommand fileare equivalent only for filters that default to stdin; they are not interchangeable in general, and the observable difference in output format is the most common symptom.
Frequently Asked Questions
Does redirection change the actual line count?
No. The count is identical in both forms. Only the presence of the filename in the output differs, because the program knows the name only when it receives one as an argument.
Why does wc -l print extra columns like word and byte counts by default?
Without -l, wc reports lines, words, and bytes. With a filename argument it also prints the name as the last column. Adding -l narrows the report to lines only, but the filename still appears if one was supplied.
Can a program find out which file was redirected into it?
Not portably. It receives an open file descriptor with no path attached. On Linux, /proc/self/fd/0 can reveal the name when stdin is a regular file, but relying on this is non-portable and fails for pipes and sockets.
Is cmd < file the same as cat file | cmd?
Functionally close — the command sees the same bytes on stdin either way — but the redirect avoids spawning an extra cat process and is generally preferred in scripts.
Why does some command output look different when piped to a file?
Programs such as ls --color=auto and grep --color=auto use isatty() to test whether stdout is a terminal and suppress color codes, progress bars, or human-friendly formatting when it is not.
How do I force color or formatting even when output is redirected?
Use the explicit override flag, e.g. ls --color=always or grep --color=always. Many tools offer an always/auto/never setting for exactly this purpose.