Why would you `cat /dev/null > /var/log/messages`?

On this bash scripting example page the author presents this script:

# Cleanup
# Run as root, of course.

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Log files cleaned up."

Why would you cat /dev/null onto anything? I can’t understand what’s intended here (is it like using while TRUE; sleep 1; elihw for {some busy program}?). Yet the author calls it “Nothing unusual.”


You typically cat /dev/null > [something] when you want to wipe file contents while ensuring there is absolutely zero risk of interruption to the actual file state. The contents of the file will clearly be wiped by the cat /dev/null yet the file itself—as it exists and is known to the file system it resides on—will still be there with the same inode number, ownership and permissions.

In the case of a log file, it could be that the log file itself is marked “in use” by another process. So doing—for example—an rm /var/log/messages && touch /var/log/messages would be disruptive to other processes and might cause running processes to choke. Meaning a process that somehow is locked to a specific inode number connected to the file /var/log/messages could suddenly panic and say, “Hey! What happened to /var/log/messages!” even if the file is still there. Not to mention potential issues with ownership and permissions being incorrectly recreated.

Because of this uncertainty in usage/state of a file the use of cat /dev/null > [something] is preferred by system admins who want to clear out a log but do not want to potentially interfere with the operation of already existing processes.

Also, in the context of the page you link to the author states the following:

There is nothing unusual here, only a set of commands that could just as easily have been invoked one by one from the command-line on the console or in a terminal window. The advantages of placing the commands in a script go far beyond not having to retype them time and again.

So the “nothing unusual” the author mentions is with regards to the whole concept of what that specific bash script is: It’s just a set of simple commands that can just as easily be run from the command line but are placed in a text file to avoid having to retype them over and over again.


Why would you cat /dev/null onto anything?

You'll do that to truncate a file contents while keeping the inode intact. All programs that have that file open for reading or writing wouldn't be affected outside the fact the file size would be reset to zero.

A bogus alternative often found is removing the file then creating it again:

rm file
touch file

or the similar:

mv file file.old
gzip file.old
touch file

The issue is these methods do not not prevent the old file from being kept written by whatever processes having the deleted file open at deletion time. The reason why is under Unix file systems, when you delete a file, you only unlink its name (path) from its content (inode). The inode is kept alive as long as there are processes having it open for reading or writing.

This leads to several negative effects: the logs written after the file deletion are lost as there is no straightforward/portable way to open a deleted file. As long as a process is writing to the deleted file, its content is still using space on the file system. That means that if you remove/create the file because it was filling your disk, the disk stays filled. One way to fix this latter issue is to restart the logger processes but you might not want to do that for critical services and intermediary logs would be definitively lost. There are also side effects due to the fact the file you create might not have the same permissions, owner and group than the original one. This for example could prevent a log analyzer to read the newly created file, or worse, prevent the logging process to write its own logs.

The first method, cat /dev/null > file achieve the goal properly however, despite a tenacious urban legend, its cat /dev/null part does absolutely nothing useful. It opens a pseudo file which is empty by design, it fails to read anything from it and finally just exits. Using this command is then a waste of keystrokes, bytes, system calls, and CPU cycles and it can be replaced without any functional change by the undoubtedly faster no-op command : or even, with most shells, by no command at all.

Let me try a metaphor to explain how useless cat /dev/null is. Let's say your goal is to empty a glass.

  • You first remove any liquid from it. That is sufficient and is precisely what (> file) does given the fact redirections are always processed first.

  • Then, you pick an empty bottle (/dev/null) and pour it in the empty glass (cat). This is the pointless step ...

If you read your linked document to the end, you might notice the comments in this line from the enhanced version of the script:

cat /dev/null > wtmp  #  ': > wtmp' and '> wtmp'  have the same effect.

They have indeed; too bad cat /dev/null was kept in the code.

That means that the following code will work with all common shells (both csh and sh families):

cd /var/log
: > messages
: > wtmp
echo "Log files cleaned up."

and this will work with all shells using the Bourne syntax, like ash, bash, ksh, zsh and the likes:

cd /var/log
> messages
> wtmp
echo "Log files cleaned up."

Note however that with ancient, pre-POSIX Bourne shells, any of these commands, including cat /dev/null won't truncate a file if it is written afterwards by a still running shell script appending to it. Instead of a zero byte file, that would be a sparse file with its size unchanged. The same would happen if the file is written by a process seeking to the position it thinks is the current one before writing.

Beware also that some alternative solutions often suggested to truncate a file do have flaws.

  • Both of the following ones just do not do the job. The resulting file is not empty but contains an empty line. This would break log files like wtmp that store fixed width records.

     echo > file
     echo "" > file
    
  • The next one based on a BSD sh option is not portable, POSIX doesn't specify any allowed options for echo so you might end up with a file containing a line with "-n":

     echo -n > file
    
  • That one not portable either by using a System V sh escape sequence. Some shells will create a file containing a line with "\c":

     echo "\c" > file
    
  • That one uses a command designed to do the job. The issue is using truncate is not portable because this command, not being specified by POSIX, might be missing from a Unix/Linux system.

     truncate -s 0
    

Finally, here are a couple of alternatives that are portable and will properly do the job:

  • Explicitly printing an empty string to the file:

     printf "" > file
    
  • Using the true command which is strictly equivalent to the no-op one : albeit more readable:

     true > file