The simplest way to display file contents is using the cat
command:
cat file.txt
I can get the same result using input redirection:
cat < file.txt
Then, what is the difference between them?
The simplest way to display file contents is using the cat
command:
cat file.txt
I can get the same result using input redirection:
cat < file.txt
Then, what is the difference between them?
cat file
The cat
program will open, read and close the file.
cat < file
Your shell will open the file and connect the contents to cat
's stdin. cat
recognizes it has no file arguments, and will read from stdin.
cat
. If there are none, cat reads from stdin. Maybe this way it is clearer. The first command has a file argument, the second doesn't.
cat file1 file2 ...
-- you can't do that with redirection.
Commented
Nov 21, 2019 at 22:26
sudo cat
of a file that can only be read by root.
-
, so it's not "only" if there are no arguments. Hopefully no one is confused by that, but, well, it happens.
Commented
Nov 22, 2019 at 20:42
There is no difference from a user point of view. These commands do the same thing.
Technically the difference is in what program opens the file: the cat
program or the shell that runs it. Redirections are set up by the shell, before it runs a command.
(So in some other commands--that is, not the command shown in the question--there may be a difference. In particular, if you can't access file.txt
but the root user can, then sudo cat file.txt
works but sudo cat < file.txt
does not.)
You can use either one that is convenient in your case.
There are almost always many ways to get the same result.
cat
accepts a file from arguments or stdin
if there are no arguments.
See man cat
:
SYNOPSIS
cat [OPTION]... [FILE]...
DESCRIPTION
Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
sudo cat /etc/shadow
v/s sudo cat < /etc/shadow
. Still the same thing? Nope - because what happens in between is different, and sometimes important to understand. :)
Commented
Nov 22, 2019 at 20:35
sudo cat file
will work, but sudo cat < file
will fail)
One big difference is with the *
, ?
, or [
globbing characters (wildcards) or anything else the shell may expand into multiple filenames. Anything the shell expands into two or more items, rather than treating as a single filename, cannot be opened for redirection.
Without redirection (ie no <
), the shell passes multiple filenames to cat
, which outputs the files' contents one after another. For example this works:
$ ls hello?.py
hello1.py hello2.py
$ cat hello?.py
# Output for two files 'hello1.py' and 'hello2.py' appear on your screen
But with redirection (<
) an error message occurs:
$ ls < hello?.py
bash: hello?.py: ambiguous redirect
$ cat < hello?.py
bash: hello?.py: ambiguous redirect
I thought with redirection it would be slower but there is no perceivable time difference:
$ time for f in * ; do cat "$f" > /dev/null ; done
real 0m3.399s
user 0m0.130s
sys 0m1.940s
$ time for f in * ; do cat < "$f" > /dev/null ; done
real 0m3.430s
user 0m0.100s
sys 0m2.043s
Notes:
cat
will never "process wildcards" in the sense of expanding or otherwise specially treating *
. If the current directory has any filenames with no leading .
, the shell expands *
to them. When *
appears in a position where arguments to cat
would appear, these filenames are passed to cat
as separate arguments. When the *
appears in a position where a filename for redirection would appear and if the *
expands to more than one filename, that makes no sense: multiple files can't be open on the same file descriptor at the same time. So bash gives the "ambiguous redirect" error.
Commented
Nov 22, 2019 at 8:30
cat
handles wildcards.
Commented
Nov 23, 2019 at 16:16
The major difference is who opens the file, shell or cat. They may be operating with different permission regimes, so
sudo cat /proc/some-protected-file
may work while
sudo cat < /proc/some-protected-file
will fail. This kind of permission regime can be a bit tricky to work around when just wanting to use echo
for easy scripting, so there is the expedience of misusing tee
like in
echo level 7|sudo tee /proc/acpi/ibm/fan
which doesn't really work using redirection instead because of the permission problem.
With cat file.txt
the application ( in this case cat
) received one positional parameter, executes open(2) syscall on it, and permission checks happen within the applications.
With cat < file.txt
the shell will perform dup2()
syscall to make stdin into a copy of file descriptor (typically next available one, e.g. 3) corresponding to file.txt
and close that file descriptor ( e.g. 3). The application does not perform open(2) on the file and is unaware of file's existence; it operates strictly on its stdin file descriptor. Permission check rests with the shell. Open file description will remain the same as when the shell opened the file.
On the surface cat file.txt
and cat < file.txt
behave the same, but there's a lot more going on behind the scenes with that single character difference. That one <
character changes how shell understands file.txt
, who opens the file, and how the file is passed between shell and the command. Of course, in order to explain all these details we also need to understand how opening files and running commands works in shell, and this is what my answer aims to achieve - educate the reader, in simplest possible terms, on what really goes on in these seemingly simple commands. In this answer you'll find multiple examples, including those that use strace command to back up the explanations of what actually happens behind the scenes.
Because inner workings how shells and commands are based on standard syscalls, viewing cat
as just one command among many others is important. If you are a beginner reading this answer, please set yourself with an open mind and be aware that prog file.txt
will not always be the same as prog < file.txt
. A different command may behave entirely differently when the two forms are applied to it, and that depends on permissions or how the program is written. I ask you also to suspend judgement, and look at this from the perspective of different users - for a casual shell user the needs may be entirely different than for sysadmin and developer.
Shells run commands by creating a child process with fork(2) syscall and calling execve(2) syscall, which executes command with specified arguments and environment variables. The command called inside execve()
will take over and replace the process; for instance, when shell calls cat
it will first create a child process with PID 12345 and after execve()
happens the PID 12345 becomes cat
.
This brings us to the difference between cat file.txt
and cat < file.txt
. In the first case, cat file.txt
is a command called with one positional parameter, and the shell will put together execve()
appropriately:
$ strace -e execve cat testfile.txt
execve("/bin/cat", ["cat", "testfile.txt"], 0x7ffcc6ee95f8 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++
In the second case, the <
part is shell operator and < testfile.txt
tells the shell to open testfile.txt
and make stdin file descriptor 0 into a copy of file descriptor which corresponds to testfile.txt
. This means < testfile.txt
is not going to be passed to the command itself as positional argument:
$ strace -e execve cat < testfile.txt
execve("/bin/cat", ["cat"], 0x7ffc6adb5490 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++
$
This can be significant if the program requires a positional parameter to function properly. In this case, cat
defaults to accepting input from stdin if no positional parameters corresponding to files were supplied. Which also brings us to the next topic: stdin and file descriptors.
Who opens the file - cat
or shell ? How do they open it ? Do they even have permission to open it ? These are the questions that can be asked, but first we need to understand how opening a file works.
When a process performs open()
or openat()
on a file, those functions provide the process with an integer corresponding to the open file, and the programs then can call read()
, seek()
, and write()
calls and myriad of other syscalls by referring that integer number. Of course the system ( aka kernel ) will keep in memory how a particular file was open, with what sort of permissions, with what sort of mode - read only,write only, read/write - and where in the file we're currently - at the byte 0 or byte 1024 - which is called an offset. This is called open file description.
On the very basic level, cat testfile.txt
is where cat
opens the file and it will be referenced by next available file descriptor which is 3 (notice the 3 in read(2)).
$ strace -e read -f cat testfile.txt > /dev/null
...
read(3, "hello, I am testfile.txt and thi"..., 131072) = 79
read(3, "", 131072) = 0
+++ exited with 0 +++
By contrast, cat < testfile.txt
will use file descriptor 0 ( aka stdin ):
$ strace -e read -f cat < testfile.txt > /dev/null
...
read(0, "hello, I am testfile.txt and thi"..., 131072) = 79
read(0, "", 131072) = 0
+++ exited with 0 +++
Remember when earlier we learned that shells run commands via fork()
first then exec()
type of process ? Well, turns out how file is open caries over to the child processes created with fork()/exec()
pattern. To quote open(2) manual:
When a file descriptor is duplicated (using dup(2) or similar), the duplicate refers to the same open file description as the original file descriptor, and the two file descriptors consequently share the file offset and file status flags. Such sharing can also occur between processes: a child process created via fork(2) inherits duplicates of its parent's file descriptors, and those duplicates refer to the same open file descriptions
What does this mean for cat file.txt
vs cat < file.txt
? A lot actually. In cat file.txt
the cat
opens the file, which means it's it is in control of how file is opened. In the second case, shell will open the file.txt
and how it was opened will remain unchanged for child processes, compound commands, and pipelines. Where we're currently at in the file will also remain the same.
Let's use this file as an example:
$ cat testfile.txt
hello, I am testfile.txt and this is first line
line two
line three
last line
Look at example below. Why didn't the word line
change in the first line ?
$ { head -n1; sed 's/line/potato/'; } < testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
potato two
potato three
last potato
The answer lies in the quote from open(2) manual above: the file opened by the shell is duplicated onto stdin of the compound command and each command/process that runs shares the offset of the open file description. head
simply rewinded the file ahead by one line, and sed
dealt with the rest. More specifically, we'd see 2 sequences of dup2()
/fork()
/execve()
syscalls, and in each case we get the copy of file descriptor which references the same file description on the open testfile.txt
. Confused ? Let's take a bit crazier example:
$ { head -n1; dd of=/dev/null bs=1 count=5; cat; } < testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
two
line three
last line
Here we printed first line, then rewinded open file description 5 bytes ahead ( which eliminated the word line
) and then just printed the rest. And how did we manage to do it ? The open file description on testfile.txt
remains the same, with shared offset on the file.
Now, why this is useful to understand, aside from writing crazy compound commands like above ? As a developer you might want to take advantage or beware of such behavior. Let's say instead of cat
you wrote a C program that needs a configuration either passed as file or passed from stdin, and you run it like myprog myconfig.json
. What will happen if instead you ran { head -n1; myprog;} < myconfig.json
? At best your program will get incomplete config data, and at worst - break the program. We can also use that as an advantage to spawn child process and let parent rewind to data which child process should take care of.
Let's start with an example this time on a file with no read or write permissions to other users:
$ sudo -u potato cat < testfile.txt
hello, I am testfile.txt and this is first line
line two
line three
last line
$ sudo -u potato cat testfile.txt
cat: testfile.txt: Permission denied
What happened here ? Why can we read the file in first example as potato
user but not in second ? This goes back to the same quote from open(2) man page mentioned earlier. With < file.txt
shell opens the file, hence permission checks happen at the time of open
/openat()
performed by shell. The shell at that time runs with privileges of the file owner who does have read permissions on the file. By virtue of open file description being inherited across dup2
calls, the shell passes copy of open file descriptor to sudo
, which passed copy of file descriptor to cat
, and cat
being unaware of anything else happily reads the contents of the file. In the last command, the cat
under potato user performs open()
on the file, and of course that user has no permission to read the file.
More practically and more commonly, this is why users are baffled as to why something like this doesn't work (running privileged command to write to file which they cannot open):
$ sudo echo 100 > /sys/class/drm/*/intel_backlight/brightness
bash: /sys/class/drm/card0-eDP-1/intel_backlight/brightness: Permission denied
But something like this works (using a privileged command to write to file that dos require privileges):
$ echo 100 |sudo tee /sys/class/drm/*/intel_backlight/brightness
[sudo] password for administrator:
100
A theoretical example of the opposite situation from the one I showed earlier ( where privileged_prog < file.txt
fails but privileged_prog file.txt
does work ) would be with SUID programs. The SUID programs , such as passwd
, allow performing actions with permissions of the executable owner. This is why passwd
command allows you to change your password and then write that change to /etc/shadow even though the file is owned by root user.
And for the sake of example and fun, I actually write quick demo cat
-like application in C (source code here) with SUID bit set, but if you get the point - feel free to skip to next section of this answer and ignore this part. Side note: the OS ignores SUID bit on interpreted executables with #!
, so a Python version of this same thing would fail.
Let's check the permissions on the program and the testfile.txt
:
$ ls -l ./privileged
-rwsr-xr-x 1 administrator administrator 8672 Nov 29 16:39 ./privileged
$ ls -l testfile.txt
-rw-r----- 1 administrator administrator 79 Nov 29 12:34 testfile.txt
Looks good, only the file owner and those who belong to administrator
group can read this file. Now let's login as potato user and try to read the file:
$ su potato
Password:
potato@my-PC:/home/administrator$ cat ./testfile.txt
cat: ./testfile.txt: Permission denied
potato@my-PC:/home/administrator$ cat < ./testfile.txt
bash: ./testfile.txt: Permission denied
Looks OK, neither shell nor cat
that have potato user permissions can read the file they're not allowed to read. Notice also who reports the error - cat
vs bash
. Let's test our SUID program:
potato@my-PC:/home/administrator$ ./privileged testfile.txt
hello, I am testfile.txt and this is first line
line two
line three
last line
potato@my-PC:/home/administrator$ ./privileged < testfile.txt
bash: testfile.txt: Permission denied
Works as intended ! Again, the point made by this little demo is that prog file.txt
and prog < file.txt
differ in who open the file and differ in open file permissions.
We already know that < testfile.txt
re-writes stdin in such way that data will come from the specified file instead of keyboard. In theory, and based on Unix philosophy of "doing one thing and doing it well", programs reading from stdin ( aka file descriptor 0 ) should behave consistently, and as such prog1 | prog2
should be similar to prog2 file.txt
. But what if prog2
wants to rewind with lseek syscall, for example in order to skip to certain byte or rewind to the end in order to find how much data we have ?
Certain programs disallow reading data from pipe, since pipelines cannot be rewinded with lseek(2) syscall or the data cannot be loaded into memory with mmap(2) for faster processing. This has been covered by an excellent answer from Stephane Chazelas in this question: What is the difference between “cat file | ./binary” and “./binary < file”? I highly recommend reading that.
Luckily, cat < file.txt
and cat file.txt
behaves consistently and cat
is not against pipes in any way, although we know it reads entirely different file descriptors. How does this apply in prog file.txt
vs prog < file.txt
in general ? If a program really doesn't want to do anything with pipes, lacking positional parameter file.txt
will be enough to exit with error, but the application can still use lseek()
on stdin to check it it is a pipe or not (although isatty(3) or detecting S_ISFIFO mode in fstat(2) are more likely to be used for detecting pipe input ), in which case doing something like ./binary <(grep pattern file.txt)
or ./binary < <(grep pattern file.txt)
may not work.
A file type may influence prog file
vs prog < file
behavior. Which to some extent implies that as a user of a program you are choosing the syscalls even if you are not aware of doing so. For instance, suppose we have a Unix domain socket and we run nc
server to listen on it, maybe we even prepared some data to be served
$ nc -U -l /tmp/mysocket.sock < testfile.txt
In this case, /tmp/mysocket.sock
will be opened via different syscalls:
socket(AF_UNIX, SOCK_STREAM, 0) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_UNIX, sun_path="/tmp/mysocket.sock"}, 20) = 0
Now, let's try to read data from that socket in different terminal:
$ cat /tmp/mysocket.sock
cat: /tmp/mysocket.sock: No such device or address
$ cat < /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address
Both the shell and cat are performing open(2)
syscall on what requires entirely different syscall - the socket(2) and connect(2) pair. Even this doesn't work:
$ nc -U < /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address
But if we are conscious of the file type and how we can invoke the proper syscall, we can get the desired behavior:
$ nc -U /tmp/mysocket.sock
hello, I am testfile.txt and this is first line
line two
line three
last line
The quote from open(2) manual states that permissions on file descriptor get inherited. In theory, there is a way to change read/write permissions on a file descriptor but that has to be done on the level of source code.
What is an open file description?. See also POSIX definition
Why is the behavior of command 1>file.txt 2>file.txt
different from command 1>file.txt 2>&1
?