Is it a UUOC (useless use of cat) to redirect one file to another?

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.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

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
$

Method 3

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

Method 4

< 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

Method 5

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

edit: deleted false statement

Method 6

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.

Method 7

If it works, don’t fix it.

I would use

cat < file1 > file2

and not sweat the PC of the semantics.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x