TL;DR
Open your log file in append mode:
cmd >> log
Then, you can safely truncate it with:
: > log
Details
With a Bourne-like shell, there are 3 main ways a file can be open for writing. In write-only (>
), read+write (<>
) or append (and write-only, >>
) mode .
In the first two, the kernel remembers the current position you (by you, I mean, the open file description, shared by all the file descriptors that have duplicated or inherited it by forking from the one you opened the file on) are into the file.
When you do:
cmd > log
log
is open in write-only mode by the shell for the stdout of cmd
.
cmd
(its initial process spawned by the shell and all the possible children) when writing to their stdout, write at the current cursor position held by the open file description they share on that file.
For instance, if cmd
initially writes zzz
, the position will be at byte offset 4 into the file, and the next time cmd
or its children write to the file, that's where the data will be written regardless of whether the file has grown or shrunk in the interval.
If the file has shrunk, for instance if it has been truncated with a
: > log
and cmd
writes xx
, those xx
will be written at offset 4
, and the first 3 characters will be replaced by NUL characters.
$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000 z z z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000 z z z a a a a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000 z z z b b a a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000 \0 \0 \0 \0 \0 x
0000006
That means you cannot truncate a file that has been open in write-only mode (and that's the same for read+write) as if you do, processes that had file descriptors open on the file, will leave NUL characters at the beginning of the file (those, except on OS/X, usually don't take space on disk though, they become sparse files).
Instead (and you'll notice most applications do that when they write to log files), you should open the file in append mode:
cmd >> log
or
: > log && cmd >> log
if you want to start on an empty file.
In append mode, all writes are made at the end of the file, regardless of where the last write was:
$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000 a a x b b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000 c c
0000002
That's also safer as if two processes have open (in that way) the file by mistake (as for instance if you've started two instances of the same daemon), their output will not overwrite each other.
On recent versions of Linux, you can check the current position and whether a file descriptor has been open in append mode by looking at /proc/<pid>/fdinfo/<fd>
:
$ cat /proc/self/fdinfo/4
pos: 2
flags: 0102001
Or with:
$ lsof +f G -p "$$" -ad 4
COMMAND PID USER FD TYPE FILE-FLAG DEVICE SIZE/OFF NODE NAME
zsh 4870 root 4w REG 0x8401;0x0 252,18 2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND PID USER FD TYPE FILE-FLAG DEVICE SIZE/OFF NODE NAME
zsh 4870 root 4w REG W,AP,LG 252,18 2 59431479 /home/chazelas/log
Those flags correspond to the O..._ flags passed to the open
system call.
$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01
(O_APPEND
is 0x400 or octal 02000)
So the shell's >>
opens the file with O_WRONLY|O_APPEND
(and 0100000 here is O_LARGEFILE which is not relevant to this question) while >
is O_WRONLY
only (and <>
is O_RDWR
only).
If you do a:
sudo lsof -nP +f g | grep ,AP
to search for files open with O_APPEND
, you'll find most log files currently open for writing on your system.
syslogd
orlogrotate
./my_app >> log
(to force appending) andcp /dev/null log
to truncate it?cat "" > log
isn't a validcat
command since there's no file called""
.