diff --git a/file_utils/unln.py b/file_utils/unln.py index 9f2b9a5..7876eb5 100755 --- a/file_utils/unln.py +++ b/file_utils/unln.py @@ -27,9 +27,6 @@ # # This script separates file names given as arguments from their respective # inodes by doing the equivalent of (cp -p file tmp && mv tmp file). -# -# TODO: -# - Option to follow symlinks import argparse @@ -64,12 +61,28 @@ def quote(string): """ Quotes a string. """ return "``{}ยดยด".format(string) +def dereference(path): + """Follows a symbolic link recursively. + + Returns the final target's path, that may or may not exist in the file + system. If 'path' is not a symbolic link, it is returned as is. + """ + if not os.path.islink(path): + return path + dereferenced = os.readlink(path) + # If it's a relative link we must convert it to an absolute path + if not os.path.isabs(dereferenced): + dereferenced = os.path.join(os.path.dirname(path), dereferenced) + return dereference(dereferenced) + # Parse command-line arguments arg_parser = argparse.ArgumentParser( description="Separate a file name from its other hard links", epilog="For more information about this program, see the README file \ provided with the distribution.") +arg_parser.add_argument("-L", "--dereference", action="store_true", + help="follow symbolic links") arg_parser.add_argument("-s", "--sync", action="store_true", help="sync files after a copy to be more fail safe \ (requires Python 3.3 or above, will be ignored with lower versions)") @@ -87,16 +100,23 @@ error_filenames = [] # Main loop for filename in options.filenames: + # Is a symbolic link? + if os.path.islink(filename): + if not options.dereference: + warn(quote(filename), "is a symbolic link, ignoring.") + continue + + # Dereference the link + dereferenced = dereference(filename) + verbose("Dereferenced {} -> {}.".format( + quote(filename), quote(dereferenced))) + filename = dereferenced + # Exists? if not os.path.exists(filename): warn(quote(filename), "does not exist, ignoring.") continue - # Is a symbolic link? - if os.path.islink(filename): - warn(quote(filename), "is a symbolic link, ignoring.") - continue - # Is a regular file? if not os.path.isfile(filename): warn(quote(filename), "is not a regular file, ignoring.")