Are these two commands any different on how they go about zero-ing out files? Is the latter a shorter way of doing the former? What is happening behind the scenes?
Both
$ cat /dev/null > file.txt $ > file.txt
yield
-rw-r--r-- 1 user wheel 0 May 18 10:33 file.txt
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 /dev/null > file.txt is a useless use of cat.
Basically cat /dev/null simply results in cat outputting nothing. Yes it works, but it’s frowned upon by many because it results in invoking an external process that is not necessary.
It’s one of those things that is common simply because it’s common.
Using just > file.txt will work on most shells, but it’s not completely portable. If you want completely portable, the following are good alternatives:
true > file.txt : > file.txt
Both : and true output no data, and are shell builtins (whereas cat is an external utility), thus they are lighter and more ‘proper’.
Update:
As tylerl mentioned in his comment, there is also the >| file.txt syntax.
Most shells have a setting which will prevent them from truncating an existing file via >. You must use >| instead. This is to prevent human error when you really meant to append with >>. You can turn the behavior on with set -C.
So with this, I think the simplest, most proper, and portable method of truncating a file would be:
:>| file.txt
Method 2
In terms of portability:
Bourne POSIX zsh csh/tcsh rc/es fish > file Y Y N(1) N(1) N N : > file N/Y(2) Y(3) Y Y(4) N(5) N(5) true > file Y(5) Y Y Y(5) Y(5) Y(5) cat /dev/null > file Y(5) Y Y(5) Y(5) Y(5) Y(5) eval > file Y(3,8) Y(3) Y Y(6) Y Y cp /dev/null file (7) Y(5) Y Y(5) Y(5) Y(5) Y(5) printf '' > file Y(5) Y Y Y(5) Y(5) Y
Notes:
- except in
shorkshemulation, for redirections without a command, in zsh, a default command is assumed (a pager for stdin redirection only,catotherwise), that can be tuned with the NULLCMD and READNULLCMD variables. That’s inspired from the similar feature in(t)csh - Redirections were initially not performed for
:in UnixV7 as:was interpreted half-way between a comment leader and a null command. Later they were and like for all builtins, if the redirection fails, that exits the shell. :andevalbeing special built-ins, if the redirection fails, that exits the shell (bashonly does that in POSIX mode).- Interestingly, in
(t)csh, that’s defining a null label (forgoto), sogoto ''there would branch there. If the redirection fails, that exits the shell. - Unless/if the corresponding command is available in
$PATH(:generally isn’t;true,cat,cpandprintfgenerally are (POSIX requires them)). - If the redirection fails, that exits the shell.
- If
fileis a symlink to an non-existing file however, somecpimplementations like GNU’s will refuse to create it. - The initial versions of the Bourne shell didn’t support redirecting builtins though
In terms of legibility:
(this section is highly subjective)
> file. That>looks too much like a prompt or a comment. Also the question I’ll ask when reading that (and most shells will complain about the same) is what output exactly are you redirecting?.: > file.:is known as the no-op command. So that reads straight away as generating an empty file. However, here again, that:can easily be missed and/or seen as a prompt.true > file: what has boolean to do with redirection or file content? What is meant here? is the first thing that comes to my mind when I read that.cat /dev/null > file. Concatenate/dev/nullintofile?catbeing often seen as the command to dump the content of the file, that can still make sense: dump the content of the empty file intofile, a bit like a convoluted way to saycp /dev/null filebut still understandable.cp /dev/null file. Copies the content of the empty file tofile. Makes sense, though someone not knowing howcpis meant to do by default might think you’re trying to makefileanulldevice as well.eval > fileoreval '' > file. Runs nothing and redirects its output to afile. Makes sense to me. Strange that it’s not a common idiom.printf '' > file: explicitly prints nothing into a file. The one that makes most sense to me.
In terms of performance
The difference is going to be whether we’re using a shell builtin or not. If not, a process has to be forked, the command loaded and executed.
eval is guaranteed to be built in all shells. : is built-in wherever it’s available (Bourne/csh likes). true is builtin in Bourne-like shells only.
printf is built-in most modern Bourne-like shells and fish.
cp and cat generally are not built-in.
Now cp /dev/null file does not invoke shell redirections, so things like:
find . -exec cp /dev/null {} ;
are going to be more efficient than:
find . -exec sh -c '> "$1"' sh {} ;
(though not necessarily than:
find . -exec sh -c 'for f do : > "$f"; done' sh {} +
).
Personally
Personally, I use : > file in Bourne-like shells, and don’t use anything other than Bourne-like shells these days.
Method 3
You might want to look at truncate, which does exactly that: truncate a file.
For example:
truncate --size 0 file.txt
This is probably slower than using true > file.txt.
My main point however is: truncate is intended for truncating files, while using > has the side effect of truncating a file.
Method 4
The answer depends a bit on what file.txt is, and how process write to it!
I’ll cite a common use case : you have a growing logfile called file.txt, and want to rotate it.
Therefore you copy, for example, file.txt into file.txt.save, then truncate file.txt.
In this scenario, IF the file is not opened by another_process (ex: another_process could be a program outputting to that file, for example a program logging something), then your 2 proposals are equivalent, and both work well (but the 2nd is prefered as the first “cat /dev/null > file.txt” is a Useless Use of Cat and also opens and reads /dev/null).
But the real trouble would be if the other_process is still active, and still has an open handle going to the file.txt.
Then, 2 main cases arise, depending on how other process opened the file :
-
If
other_processopens it in the normal way, then the handle will be still pointing to the former location in the file, for example at offset 1200 bytes. The next write will therefore start at offset 1200, and thus you’ll have again a file of 1200bytes (+ whatever other_process wrote), with 1200 leading null characters! Not what you want, I presume. -
If
other_processopenedfile.txtin “append mode”, then each time it writes, the pointer will actively seek to the end of the file. Therefore, when you truncate it, it will “seek” until byte 0, and you won’t have the bad side effect! This is what you want (… usually!)
Note that this means you need, when you truncate a file, to make sure that all other_process still writing to that location have opened it in the “append” mode. Otherwise you’ll need to stop those other_process, and start them again, so they start pointing at the beginning of the file instead of the former location.
References : https://stackoverflow.com/a/16720582/1841533 for a cleaner explanation, and a nice short example of difference between normal and append mode logging at https://stackoverflow.com/a/984761/1841533
Method 5
I like this and use it often because it looks cleaner and not like somebody hit the return key by accident:
echo -n "" > file.txt
Should be a built-in too?
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