30

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?

4

5 Answers 5

63
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.

6
  • 2
    Arguments are file names that are written after 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.
    – Pilot6
    Commented Nov 21, 2019 at 22:14
  • 4
    Additionally, you can provide several filenames to cat and it will dutifully concatenate them all in order: cat file1 file2 ... -- you can't do that with redirection. Commented Nov 21, 2019 at 22:26
  • 9
    The shell will not read it or send any contents anywhere. It will open it and dup2() its file descriptor to the child proc's stdin. The child proc (in this case cat) will do the reading, from its stdin descriptor.
    – asveikau
    Commented Nov 22, 2019 at 7:25
  • 8
    Though not mentioned in the question, this difference is particularly relevant when doing sudo cat of a file that can only be read by root.
    – cbojar
    Commented Nov 22, 2019 at 13:09
  • 5
    FWIW, cat also reads from stdin if one of the arguments is -, so it's not "only" if there are no arguments. Hopefully no one is confused by that, but, well, it happens.
    – dannysauer
    Commented Nov 22, 2019 at 20:42
17

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.
15
  • 21
    There is no difference as far as output goes, but there is difference how and who opens the file. In this case also cat is OK, but if it is a program that disalows reading stdin - it will be very different behavior Commented Nov 21, 2019 at 21:23
  • 6
    Perhaps an analogy: What's the difference between dying peacefully your sleep v/s being tortured to death over an extended period? There's no difference if you only look at the before and after. But if you're paying attention in between, there's a whole lot of difference in what happens. :D That difference matters in several circumstances. Consider a really common example like 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. :)
    – dannysauer
    Commented Nov 22, 2019 at 20:35
  • 3
    I just down voted because of the absolute statement "there is no difference". As my answer (which also just go downvoted) illustrates there is a difference. If you change your answer to "there is little difference" I will gladly retract my down vote just now. Commented Nov 23, 2019 at 4:41
  • 1
    @Pilot6 Thanks for edit. I've retracted down vote. Commented Nov 23, 2019 at 15:25
  • 1
    One other notable difference which is a direct result of the "which thing opens the file" point: If you are running cat via something that changes the user/permissions it runs under (such as "sudo"), this results in a difference of what user actually opens the file, which can change whether it succeeds or not. (e.g. If "file" is only readable by root, sudo cat file will work, but sudo cat < file will fail)
    – Foogod
    Commented Nov 25, 2019 at 0:58
14

One Big Difference

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

One Tiny Difference

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:

  • The difference is about 1/1000th (1 one thousandth) of a second in this test. In other tests it was 1/100th of a second which is still can't be noticed.
  • Alternate the tests a few times so data is cached into RAM as much as possible and more consistent comparison times are returned. Another option is to drop all caches before each test.
7
  • 20
    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
  • Repeat the tests a few times so data is cached into RAM as much as possible Why? Why to cache the data to affect the test results?
    – BlueSkies
    Commented Nov 22, 2019 at 14:10
  • 1
    Fair enough. I've reworded the cahce notes to make reasons clearer. Commented Nov 22, 2019 at 16:14
  • @EliahKagan I removed the inference that cat handles wildcards. Commented Nov 23, 2019 at 16:16
  • 1
    There was not a wildcard in OP’s question.
    – WGroleau
    Commented Nov 24, 2019 at 7:58
10

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.

1
  • OP’s question does not involve sudo. Can there be similar permissions issues without it?
    – WGroleau
    Commented Nov 24, 2019 at 8:00
9

TL;DR Version of the answer:

  • 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.

Introduction

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.

execve() Syscall and Positional Parameters the Executable Sees

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.

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.

Permissions and Privileges

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.

How Programs React to STDIN

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.

Filetype influence

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

Notes and other suggested readings:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .