Those nasty little dot files…

When I tried to setup a Gitlab continuous deployment, I encountered a tiny problem: How to remove all files within a Linux directory?

Apparently it is quite easy. Just with a simple

rm -rf dir/*

Wrong!

The above command rm -r dir/* only remove all visible files and directories from the directory dir. It does not remove hidden files and directories.

Similarly, I also had to copy all files from one directory to another.

Why not delete the whole directory

Someone might say: The whole “deleting all files” process just seems useless! Why not remove the directory as a whole? Something like

rm -rf dir/
mkdir dir

That is true, until you consider dir’s file permissions. If you remove dir as a whole and creates a new dir, the old dir’s permissions are not automagically copied to the new one.

Imagine such scenario: you have a directory dir with owner some_user:some_group and permission 0700, and you are logged in using root account.

Executing rm -rf dir/ && mkdir dir with root makes a new directory with owner root:root and permission 0755, quite different from before.

It is not a good idea. And directly copying contents of a directory into another suffers the same issue.

The good thing is, only deleting contents of a directory is possible.

Deleting a directory’s content including hidden ones

A widely used command for deleting directory contents is the

rm -rf dir/{,.}*

It basically expands to

rm -rf dir/* dir/.*

There is still a small issue though: as bash’s .* matches everything beginning with a dot, the current directory . and parent directory .. are also matched.

It is not an issue when entering commands manually, as rm will rage quit those two before continuing on deleting other files. The problem is, the rm command will exit with error 1.

So we need something different. The below command performs the task perfectly:

rm -rf dir/{..?*,.[!.]*,*}

It expands to

rm -rf dir/..?* dir/.[!.]* dir/*

which includes,

  1. Everything beginning with two dots except .. itself
  2. Everything beginning with only one dot
  3. All visible files and directories

Hooray!

Copying contents from a directory to another (Easy trick!)

We can use the same trick {..?*,.[!.]*,*} from above, but there is an easier alternative:

cp dir/. new_dir/

I have no idea how it works, but it does. The symbol . does nothing in Bash, and is something handled by cp itself.

My guess is that cp attempts to copy the directory dir/. to new_dir/. and since . is the name of the current directory, it copies contents of dir to new_dir directly.

So, a neat little trick than no one knows how it works! And it is much easier to read than the Bash trick {..?*,.[!.]*,*}.

Other options

You can also modify the behavior of Bash’s * symbol to match hidden files.

shopt -s dotglob

But, ugh, this might cause some issues when you want to match * with visible files only later.