How do I create an empty file (0 byte size) in all the directories?
Recently I was asked, in a job interview, "how to create a zero size file [I think that means an empty file] in all the folders of the file system?"
I found the question a bit strange, I thought of a loop to list all the directory and use touch
or maybe go to the root directory and use touch
with a recursive option. Do you have any ideas?
That could be ...
find . -type d -exec touch {}/emptyfile \;
-
-type d
means "directories" -
exec
ute the commandtouch
and create a file named "emptyfile" - the
{}
substitutes what is found as a result fromfind
. The/
is to make it a valid path+filenane and the escaped ; is to close the command (otherwise it becomes "emptyfile;")
result ...
rinzwind@schijfwereld:~/t$ mkdir 1 2 3 4 5 6 7 8 9 10
rinzwind@schijfwereld:~/t$ ls -ltr */*
ls: cannot access '*/*': No such file or directory
rinzwind@schijfwereld:~/t$ find . -type d -exec touch {}/emptyfile \;
rinzwind@schijfwereld:~/t$ ls -ltr */*
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 5/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 9/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 1/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 8/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 3/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 2/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 10/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 7/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 6/emptyfile
-rw-rw-r-- 1 rinzwind rinzwind 0 apr 16 19:57 4/emptyfile
Mine works but the answer from Peter Cordes is better :)
To do this efficiently, you want to avoid spawning a new touch
process for every file you want to create.
This is part of what xargs
is good for, batching args into chunks as large as possible, while small enough to fit on the command line of a single process. (Modern Linux has pretty huge limits so it's rarely a problem anymore, xargs rarely has to actually run your command multiple times to handle all the args, but it also lets you avoid having filename subjected to shell word-splitting like in foo $(bar)
.)
We can use find's own -printf
to format additional stuff onto each directory path, specifically the filename we want. The xargs -0
uses a '\0'
byte as a separator so this is safe with arbitrary filenames, even including newline. If you weren't using a custom printf format, you could just use -print0
to print 0-separated paths.
find . -xdev -type d -printf '%p/empty\0' | xargs -0 echo touch
(in a test directory, that prints touch ./empty ./2/empty ./1/empty
, not touch ./empty
, touch ./1/empty
, etc. so it's running one touch
for multiple files.)
mktemp
only accepts a single template, but if we want some randomness in the naming to reduce the chance of just touching an existing file by accident, you could do this.
find . -xdev -type d -printf "%p/empty.$RANDOM\0" | xargs -0 echo touch
Note that it's the same 15-bit random number in every directory, because "$RANDOM"
is expanded once by bash before find starts. You could use $(date +%s).$RANDOM
or whatever you want as part of the filename.
With an SSD or tmpfs, CPU might be the bottleneck here. Or if you're lucky and metadata I/O on a magnetic disk happens to be mostly contiguous because you're touching every directory (and allocating a bunch of new inodes), even a rotational disk could maybe keep up somewhat decently. Although you're probably not touching directories in the order they're laid out on disk.
And regardless, there's no need to waste lots of CPU time starting processes for something that should be I/O limited.
Ways that don't work:
-
find -exec touch {} +
batches args, but-exec touch {}/empty +
refuses to work when{}
isn't by itself. -
xargs -I {} echo touch {}/emptyfile
implies-L 1
(only process one "line" of input for each invocation of the command, whether that's an actual line or a 0-separated string withxargs -0
). So we can't use xargs to modify each arg if we want to take advantage of it for batching args.
find /mountpoint -xdev -type d -exec mktemp -p {} \;
A quite obvious aspect is you may or may not need root access to actually create files under /mountpoint
.
There are two non-obvious aspects:
-
You said "in all the folders of the file system", so we start from specific
/mountpoint
and do not enter other filesystems (-xdev
).If there are other filesystems mounted deeper in the tree, e.g. in
/mountpoint/foo/another/mntpoint
, then-xdev
will prevent us from entering them. Still these filesystems may mask entire subtrees that belong to the filesystem in question. In the best case a filesystem mounted in/mountpoint/foo/another/mntpoint
masks an emptymntpoint
directory of the filesystem in question. So we cannot easily reach "all the folders of the file system".With root access we can
mount --bind /mountpoint /somewhere/else
beforehand. With--bind
(as opposed to--rbind
, seeman 8 mount
)mntpoint
deep in/somewhere/else
will not replicate the submount from/mountpoint/foo/another/mntpoint
. This way we can accessmntpoint
that belongs to the filesystem in question.This is still not enough. If the filesystem in question is Btrfs then possibly
/mountpoint
gives access to some subvolume but not to the entire filesystem (compare this question).In general a subtree of any(?) mounted filesystem can be bind-mounted to another directory. After you unmount the original mountpoint, the other directory gives access to a fragment of the filesystem. Our
/mountpoint
may be "the other directory" in the first place and therefore it may not give access to the entire filesystem. You don't know this in advance.The conclusion is: if the phrase is strictly "all the folders of the file system" (as opposed to "all subdirectories" which is quite straightforward) then you need to make sure you don't miss any part of the filesystem. Only then use the
find …
command given at the beginning of this answer. -
Solutions with
touch emptyfile
or so do not necessarily "create a zero size file". What if the interviewer has already created a non-emptyemptyfile
in one of the directories? A trap! If non-emptyemptyfile
exists thentouch
will neither create it, nor the file will be empty. Effectively you will strictly fail to "create a zero size file" in the directory with the trap. This is the reason I usedmktemp
. The tool will try hard to really create a new empty regular file.
This will create a unique name empty file in each directory starting with and descending from the current directory including hidden directories if you want to.
First, get the directories list with tree
.
Then, pass them to xargs
like so:
tree --noreport -dfi | xargs -L 1 -I {} echo touch {}/emptyfile_"$(date +%s)"
Or, to a while
loop like so:
tree --noreport -dfi | \
while read -r d; do echo touch "$d"/emptyfile_"$(date +%s)"; done
Or, even to a for
loop (if the directory names contain no spaces) like so:
for d in $(tree --noreport -dfi); do echo touch "$d"/emptyfile_"$(date +%s)"; done
-
echo
is there to prevent unintentional creation of files while testing. When satisfied with the output, removeecho
to create files. -
--noreport
omits printing of the file and directory report at the end of the tree listing. -
-dfi
lists directories only, prints the full path prefix for each directory and makes tree not print the indentation lines. -
Use
-dfia
instead of-dfi
to include hidden directories as well. -
"$(date +%s)"
appends current timestamp to filename like thisemptyfile_1618679443
making it unique from existing files in each directory. Notice you can change this to a random number like67639871206723
if you need a fixed file name. -
xargs -L 1 -I {}
reads the input one line at a time and assigns it to{}
.