I have a folder which contains a certain number of files which have hard links (in the same folder or somewhere else), and I want to de-hardlink these files, so they become independant, and changes to their contents won't affect any other file (their link count becomes 1).
Below, I give a solution which basically copies each hard link to another location, then move it back in place.
However this method seems rather crude and error-prone, so I'd like to know if there is some command which will de-hardlink a file for me.
Crude answer :
Find files which have hard links (Edit: To also find sockets etc. that have hardlinks, use find -not -type d -links +1
) :
find -type f -links +1 # files only
find -not -type d -links +1 # files, sockets etc.
A crude method to de-hardlink a file (copy it to another location, and move it back) :
Edit: As Celada said, it's best to do a cp -p below, to avoid loosing timestamps and permissions. Edit: Create a temporary directory and copy to a file under it, instead of overwriting a temp file, it minimizes the risk to overwrite some data, though the mv
command is still risky (thanks @Tobu). Edit: Try to create the temporary directory in the same filesystem (@MikkoRantalainen).
# This is unhardlink.sh
set -e
for i in "$@"; do
temp="$(mktemp -d -- "${i%/*}/hardlnk-XXXXXXXX")"
[ -e "$temp" ] && cp -ip "$i" "$temp/tempcopy" && mv "$temp/tempcopy" "$i" && rmdir "$temp"
done
So, to un-hardlink all hard links (Edit: changed -type f
to -not -type d
, see above) :
find -not -type d -links +1 -print0 | xargs -0 unhardlink.sh
cp -i
switch, it spat at me a few messages asking if it should override./fileXXXXXX
(the$temp
file), even though tmpfile should give unique file names, so there must be some kind of race condition or whatever, and with it the risk to loose some data.unhardlink.sh
should create temporary directory inside the same directory that contains the file that needs to be unhardlinked. Otherwise your recursive call may recurse inside another filesystem and you end up moving stuff over filesystem boundaries because your temporary directory is at current working directory. I guess you could pass"$(dirname "$i")/hardlink-XXXXXX"
as the argument to mktemp instead.fuse
filesystem, it might actually dispatchpath/to/hardlink-XXX
to a different physical storage medium thanpath/to/original-file
, but there's not much that can be done about that.