36

If I want to make the contents of file2 match the contents of file1, I could obviously just run cp file1 file2.

However, if I want to preserve everything about file2 except the contents—owner, permissions, extended attributes, ACLs, hard links, etc., etc., then I wouldn't want to run cp.* In that case I just want to plop the contents of file1 into file2.

It seems like the following would do it:

< file1 > file2

But it doesn't work. file2 is truncated to nothing and not written to. However,

cat < file1 > file2

does work.

It surprised me that the first version doesn't work.

Is the second version a UUOC? Is there a way to do this without invoking a command, merely by using redirections?

Note: I'm aware that UUOC is more of a pedantic point than a true anti-pattern.

*As tniles09 discovered, cp will in fact work in this case.

  • 3
    Whether < file1 > file2 does what you want is shell-dependent. – Michael Homer Dec 16 '15 at 20:01
  • 13
    Well, it is a Useless Use of <... – jwodder Dec 16 '15 at 20:50
  • 2
    what's an anti-pattern? – mikeserv Dec 16 '15 at 22:30
  • 6
    @jwodder - that's not true. especially when you're talking about a copy. consider what happens when file1 doesn't exist or else is unreadable and you open it with < before > output is opened, and then consider what happens when you allow cat to try to open it. – mikeserv Dec 17 '15 at 2:08
  • 3
    @JonathanLeffler In zsh an empty command with redirections invokes cat (by default), essentially running the second command. See Stéphane Chazelas' answer below for more on that than fits in a comment. – Michael Homer Dec 17 '15 at 7:09
58

cat < file1 > file2 is not a UUOC. Classically, < and > do redirections which correspond to file descriptor duplications at the system level. File descriptor duplications by themselves don’t do a thing (well, > redirections open with O_TRUNC, so to be accurate, output redirections do truncate the output file). Don’t let the < > symbols confuse you. Redirections don’t move data—they assign file descriptors to other file descriptors.

In this case you open file1 and assign that file descriptor to file descriptor 0 (<file1 == 0<file1) and file2 and assign that file descriptor to file descriptor 1 (>file2 == 1>file2).

Now that you’ve got two file descriptors, you need a process to shovel data between the two—and that’s what cat is for.

  • 11
    Maybe it's just me, but my favorite part of this answer is your use of the word "shovel." :) Very clear, thank you. – Wildcard Dec 17 '15 at 8:22
  • 1
    @Wildcard I'd have preferred "pump" over "shovel", but still a good word. +1 – Mehrdad Dec 18 '15 at 11:06
  • why is shovel a good word? – bubakazouba Dec 20 '15 at 10:01
  • 1
    One shovels a pile of dirt, one shovel full at a time, from one pile to another as data is copied buffer by buffer. It's a good analogy. – bsd Dec 25 '15 at 20:27
  • 1
    In your first sentence, you say the file descriptors are being duplicated. Are they being duplicated or reassigned (as your second paragraph, and the behavior of the feature, seem to indicate)? – Greg Bell May 9 '18 at 22:53
17

It is not, because as others have pointed out, the behavior in question is shell-dependent. As you (the OP) have pointed out, this is a bit of a pedantic, maybe even humorous?, sort of topic.

However, on GNU systems, your initial premise has another solution available: cp --no-preserve=all file1 file2. Try this out, I think it will satisfy your described situation (e.g. modifying contents of file2 while not modifying its attributes).

Example:

$ ls -l
    total 8
    -rw-r--r-- 1 tniles sambashare 16 Dec 16 12:21 fezzik
    -rw-r--r-- 1 tniles tniles     14 Dec 16 12:16 fred
$ cat *
    Lookout, world!
    Hello, world!
$ cp --no-preserve=all fred fezzik 
$ ls -l
    total 8
    -rw-r--r-- 1 tniles sambashare 14 Dec 16 12:22 fezzik
    -rw-r--r-- 1 tniles tniles     14 Dec 16 12:16 fred
$ cat *
    Hello, world!
    Hello, world!

UPDATE Actually, I just noticed that my system's cp by itself seems to preserve attributes unless -a or -p are specified. I'm using bash shell and GNU coreutils. I guess you learn something new everyday...


Test results (by Wildcard) including hard link and different permissions:

$ ls -li
total 12
913966 -rw-rw-r-- 1 vagrant vagrant 30 Dec 16 20:26 file1
913965 -rwxrw---- 2 pete    vagrant 39 Dec 16 20:35 file2
913965 -rwxrw---- 2 pete    vagrant 39 Dec 16 20:35 hardlinktofile2
$ cat file1
This is the contents of file1
$ cat file2
This is the original contents of file2
$ cp file1 file2
$ ls -li
total 12
913966 -rw-rw-r-- 1 vagrant vagrant 30 Dec 16 20:26 file1
913965 -rwxrw---- 2 pete    vagrant 30 Dec 16 20:37 file2
913965 -rwxrw---- 2 pete    vagrant 30 Dec 16 20:37 hardlinktofile2
$ cat file1
This is the contents of file1
$ cat file2
This is the contents of file1
$ 
  • Nice. I ran my own test including a hard link and different permissions and it seems that you're correct. – Wildcard Dec 16 '15 at 20:31
  • Added my test results; hope you don't mind. :) I didn't test ACLs or extended attributes but given that the inode number is preserved I'm 99% sure those would be also. – Wildcard Dec 16 '15 at 20:40
  • Nice... don't mind at all. :-) – tniles Dec 16 '15 at 20:44
13

In zsh, the shell where < file1 > file2 works, the shell does invoke cat.

For a command line that consists only of redirections and no command nor assignments, zsh invokes $NULLCMD (cat by default) unless the only redirection is a < one in which case $READNULLCMD (pager by default) is invoked instead. (that's unless zsh is in sh or csh emulation in which case it behaves like the shells it emulates).

So:

< file1 > file2

is actually the same as

cat < file1 > file2

and

< file1

is the same as

pager < file1
  • For the record, this syntax does not work for ksh93 – fpmurphy Dec 17 '15 at 1:06
8
< from > to

doesn't work because there is no command there; no process. The shell opens/creates the files and arranges the redirections (meaning that the file descriptors referencing these files are planted as 0 and 1: standard input and standard output). But there is nothing there to do execute a loop to read from standard input and write to standard output.

zsh makes this work by substituting a user-configurable command in this "null command" case. The command is not visible in the command line, but it's still there. A process is created for it and it works the same way. NULLCMD is cat by default, so < from > to actually means cat < from > to in zsh, unless NULLCMD is set to something else; it is an "implicit cat" command.

A "useless use of cat" occurs when cat is used as an intermediary to read from a file and feed the data to another process, whose file descriptor could just be connected to the original file.

If cat is removable from the situation, such that the remaining commands can still perform the same task, it is useless. If it is not removable, then it isn't useless.

# useless, removable:
$ cat archive.tar | tar tf -    #  -->  tar tf archive.tar

# not removable (in POSIX shell):
$ cat > file
abc
[Ctrl-D]

# likewise:
STRING=$(cat file)

A cat that is replacable isn't the same thing. For instance instead of cat > file we can use vi file to create the file. That's doesn't count as removal of cat, while using whatever is left to achieve the same task.

If cat is the only command in the pipeline, then of course it can't be removed; no rearrangement of whatever is left will do the equivalent job.

Some shell scripters use cat because they think it lets them move the input operand closer to the left side of the command line. However, redirections can be anywhere in the command line:

# If you're so inclined:
# move source archive operand to the left without cat:
$ < archive.tar tar xf - > listing
  • btw, you don't have to use f - for tar. tar xf - is just tar x. – dnt Dec 17 '15 at 10:59
  • @mikeserv Where does it say that cat is involved in the file creation? Answer clearly says that the shell does this. What problem with > file are you referring to? I often use it alone for truncating an existing file to zero length or ensuring such exists. This question is about why < from > to doesn't work like cat < from > to, and UUoC, not "please give me reasons why cat isn't a good substitute for cp". – Kaz Dec 17 '15 at 14:33
  • 1
    @dnt, tar is the tape archiver. Many tar implementations still work with the first tape device by default. – Stéphane Chazelas Dec 18 '15 at 12:09
1

< file1 > file2 Seems to be shell-dependent, on zsh it works, on bash not.

edit: deleted false statement

  • cp -a preserves the attributes of file1 and overwrites the attributes of file2. Opposite of desired behavior. Plus I can't tell even by looking at the man page what will happen with hard links, but I think it's safe to say that file2's hard links will not be preserved. – Wildcard Dec 16 '15 at 20:10
  • You're right, I didn't read the question careful enough. – tastytea Dec 16 '15 at 20:12
1

In addition to all the good answers, you can avoid a UUOC by simulating a cat:

awk 1 file1 > file2   # For line-oriented text, not binaries.
dd if=file1 of=file2  # Works for binary files, too.
# many more geeky ways.

These commands do not copy the file meta data, as a plain cp would.

  • True, but it's worth mentioning that they have no advantage and only drawbacks (performance, reliability) over cat. Here you do need a command to shove data between the two file descriptors and cat is one of the best one for that. See also pv which would be able to use splice() on Linux for fifos, (though it doesn't do fadvise(POSIX_FADV_SEQUENTIAL) like GNU cat does). – Stéphane Chazelas Dec 18 '15 at 12:19
  • The dd command for binary files seems good...or would cat work just as well for binary files? – Wildcard Dec 18 '15 at 19:28
  • @Wildcard cat also works for binary files (Unix generally doesn't distinguish; however, some tools specifically work line-by-line, such as awk, grep, wc, ... POSIX also defines a minimum largest line length, so in theory a line-oriented tool might refuse to deal with excessively large lines.) – Jens Dec 18 '15 at 21:55
  • 2
    @StéphaneChazelas This answer also was intended as tongue-in-cheek. Seems like, despite the season, some people are allergic to fun (not directed at you; I value your shell expertise and Opengroup standards work). – Jens Dec 18 '15 at 21:59
  • sed '' < file1 > file2 ;-) – Digital Trauma May 25 '16 at 22:39
0

If it works, don't fix it.

I would use

cat < file1 > file2

and not sweat the PC of the semantics.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.