I have this command:
cat somefile >file1 >file2
After I executed this command I can't figure out why file1
has nothing in it. It should have the output of the first file (somefile
), but nothing is in it.
Can you explain to me why it does not copy or write my output from somefile
? (file2
contains my output, but file1
contains nothing)
4 Answers 4
I think there's a difference between how you think shell redirection works and how it really does.
Except in zsh
(which implements a tee
-like behaviour behind the seen when output is redirected more than once), you cannot redirect the output of the shell multiple times and expect it to be redirected to all the locations you've specified. Instead, it will only be redirected to the last location, which in your case is file2
. The answer by Chaos provides a decent explanation on how such I/O Redirection works.
What you really want to do is:
$ cat example.txt | tee file1 > file2
tee
is a program that reads from the standard input and writes to multiple file descriptors. One of them is always the standard output. So we use tee
to write the output to file1
and then redirect its stdout to file2
.
Also, based on suggestions in the comments, this is a better way to do what you're looking for:
$ tee file1 < example.txt > file2
That approach has the advantage that it redirects the stdin
instead of trying to read over a pipe. That means that the shell now needs to spawn one fewer process. It also eliminates what is known as "Useless use of cat". By placing the output redirection after the input redirection, we avoid the output files being clobbered if the input file cannot be opened.
-
4Better,
tee file1 >file2 <example.txt
Chris Davies– Chris Davies2015年10月09日 20:31:42 +00:00Commented Oct 9, 2015 at 20:31 -
1I much prefer the
cat ... | tee
approach. Maybe the cat is useless, but it is much easier for me to follow. Is your system really so constrained that firing off an extracat
process is going to be an issue??Brandin– Brandin2015年10月10日 13:26:33 +00:00Commented Oct 10, 2015 at 13:26 -
1I agree that the
cat ... | tee
approach is cleaner. Also, a system is barely ever so constrained that it cannot handle the extra call tocat
. It's however another option.darnir– darnir2015年10月10日 14:04:40 +00:00Commented Oct 10, 2015 at 14:04 -
If you really want a clean tee command, write it as
< example.txt tee > file1 file2
This can be translated to the human saying of "Using example.txt tee it to file1 and file2"Ferrybig– Ferrybig2015年10月10日 15:20:00 +00:00Commented Oct 10, 2015 at 15:20 -
1@roaima: I presume the
cat
is not really a UUOC but a placeholder for some more complicated command. (Anybody who actually wanted to copy the contents of a file to two other files would just usecp
twice.) Omitting it obscures the general principle being used.Nate Eldredge– Nate Eldredge2015年10月10日 19:03:43 +00:00Commented Oct 10, 2015 at 19:03
What you did, is called I/O-Redirection. >file
redirects the standard output (stdout) to the given file
. In you case you did that 2 times. The shell doesn't handle a redirection of the same output multiple times .
In this case:
cat somefile >file1 >file2
The shell processes the redirections before the command (cat somefile
) is executed. That means the >
truncates file to zero length, because you override the files content. The file must be empty before the shell can execute the command. This is done with both >
redirections.
Now, the second one (>file2
) overrides the first one (>file1
), because the shell processes the redirection in order of appearance. So, the last one is the one that will effectvely be used. The output of cat somefile
will therefore be redirected to file2
and file1
will be truncated to zero length.
Redirecting stdout to multiple processes/files can be done with tee
like this:
cat somefile | tee file1 file2 file3 fileX
This will print the contents to stdout and to all files given as paramters.
-
Oh, it handles redirection of the same output multiple times. It's just doesn't handle it the way the OP expected it to. ;-) (You actually describe well how it's handled.)The Sidhekin– The Sidhekin2015年10月09日 21:52:04 +00:00Commented Oct 9, 2015 at 21:52
-
4It might be better to do something like
tee <somefile >file1 file2
, sincecat somefile | tee file1 file2 file3 fileX
dumps the contents ofsomefile
to your terminal and doesn't really need thecat
call.user2357112– user23571122015年10月10日 01:34:49 +00:00Commented Oct 10, 2015 at 1:34 -
Note that
zsh
can be used with multiple>file{1,2,3}
and it works as if you usedtee
.Alexis Wilke– Alexis Wilke2020年01月18日 00:53:11 +00:00Commented Jan 18, 2020 at 0:53
In zsh
with MULTIOS option set (as it is by default), you can use:
cat somefile >file1 >file2
as in that case, zsh
performs an action similar to tee
behind the scene. Or even:
<somefile >file1 >file2
as the default value of $NULLCMD
, the command run when there are redirections without command happens to be cat
.
cat somefile >file1 >file2
Without redirections cat somefile
would inherit stdin from the shell. Usually stdin of a shell is not closed, it already points to some file. A terminal is also a file. When you run cat somefile
in an interactive shell whose stdout is your terminal, the output of cat
goes to the terminal. In general the stdout of the shell may be something else, irrelevant, cat
will inherit it anyway.
What cat somefile >file1
does is it makes the shell:
- try to open
file1
for writing (it creates the file if it's not already there) and to truncate it to zero size (if possible); this happens beforecat
is started; - set up
file1
as stdout for the futurecat
, so thecat
will usefile1
instead of what it would use without the redirection (you don't expectcat somefile >file1
to also write to your terminal, right?).
The redirection redirects. :) The current direction is "to the terminal", the new direction is "to file1
". The new direction is taken instead of the current direction and the new direction becomes current.
The shell parses redirections from left to right. In case of cat somefile >file1 >file2
the shell first handles >file1
in the way described above. Then it handles >file2
in the same way: it opens and truncates the file, and sets up "to file2
" for the future cat
instead of the current direction which at this point is already "to file1
". This way "to file2
" replaces "to file1
" and becomes current, exactly like "to file1
" replaced "to the terminal" a moment ago.
You can add more >fileN
redirections and each one will be handled in the described way. Assuming each one succeeds, the last one will win. In your case >file2
wins.
After all the redirections are handled, cat
is started. Your cat
sees file2
as its stdout and it's not even aware file1
was involved in any way. The truncation of file1
is a "side effect", it's only because >file1
has been parsed by the shell, it has nothing to do with cat
.
You can use this mechanism to create/truncate one or more files: : >file1 >file2 ...
will try to truncate the files. :
is a no-op, it doesn't even use its stdout. Creating and truncating happens when the shell handles the redirections before starting :
.
This is how output redirections work in sh
and compatible shells. The MULTIOS option in zsh
(see this other answer) is different: the first redirection replaces the old direction (e.g. the terminal) while consecutive redirections add directions. How it happens under the hood, I won't elaborate. I just want to point out that the behavior you observed emerges from the fact in sh
(or in a compatible shell) each >fileN
is handled independently, one after another; but in zsh
there's a mode where it treats multiple output redirections more like a unit.
>
redirects the output to a file, period. Usetee(1)
if you want to duplicate output to a file and send it on.cat somefile >file1 3>file2 4>file3
. This means you start the program with two extra files opened as fileno 3 and fileno 4. Of course,cat
will ignore those, but it can be useful if you want to have different log files, for example, or you're somehow splitting the input in 3 parts, etc.