[PATCH 2/4] hardlink: add --list-duplicates and --zero

наб <[email protected]>
Newsgroups org.kernel.vger.util-linux
Message-ID <b22071e3546940d6d0fa6e1d9d03f292e18229e2.1730139540.git.nabijaczleweli@nabijaczleweli.xyz>
--list-duplicates codifies what everyone keeps re-implementing with
find -exec b2sum or src:perforate's finddup or whatever.

hardlink already knows this, so make the data available thusly,
in a format well-suited for pipeline processing
(fixed-width key for uniq/cut/&c.,
 tab delimiter for cut &a.,
 -z for correct filename handling).
---
 misc-utils/hardlink.1.adoc |  6 ++++++
 misc-utils/hardlink.c      | 21 ++++++++++++++++++++-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/misc-utils/hardlink.1.adoc b/misc-utils/hardlink.1.adoc
index b6f07ba70..b85ba09d4 100644
--- a/misc-utils/hardlink.1.adoc
+++ b/misc-utils/hardlink.1.adoc
@@ -76,6 +76,12 @@
 *-n*, *--dry-run*::
 Do not act, just print what would happen.
 
+*-l*, *--list-duplicates*::
+Don't link anything, but list the absolute path of every duplicate file, one per line, preceded by a unique 16-byte discriminator and a tab.
+
+*-z*, *--zero*::
+Separate lines with a NUL instead of a newline in *-l* mode.
+
 *-o*, *--ignore-owner*::
 Link and compare files even if their owner information (user and group) differs. Results may be unpredictable.
 
diff --git a/misc-utils/hardlink.c b/misc-utils/hardlink.c
index e4b44c6c6..643df7cf2 100644
--- a/misc-utils/hardlink.c
+++ b/misc-utils/hardlink.c
@@ -189,6 +189,8 @@ static struct options {
 	unsigned int keep_oldest:1;
 	unsigned int prio_trees:1;
 	unsigned int dry_run:1;
+	unsigned int list_duplicates:1;
+	char line_delim;
 	uintmax_t min_size;
 	uintmax_t max_size;
 	size_t io_size;
@@ -206,6 +208,7 @@ static struct options {
 	.respect_xattrs = FALSE,
 	.keep_oldest = FALSE,
 	.prio_trees = FALSE,
+	.line_delim = '\n',
 	.min_size = 1,
 	.cache_size = 10*1024*1024
 };
@@ -1152,6 +1155,10 @@ static void visitor(const void *nodep, const VISIT which, const int depth)
 
 	/* final cleanup */
 	for (other = begin; other != NULL; other = other->next) {
+		if (opts.list_duplicates && other->st.st_nlink > 1)
+			for (struct link *l = other->links; l; l = l->next)
+				printf("%016zu\t%s%c", (size_t)other, l->path, opts.line_delim);
+
 		if (ul_fileeq_data_associated(&other->data))
 			ul_fileeq_data_deinit(&other->data);
 	}
@@ -1182,6 +1189,8 @@ static void __attribute__((__noreturn__)) usage(void)
 	        "                              lowest hardlink count\n"), out);
 	fputs(_(" -M, --minimize             reverse the meaning of -m\n"), out);
 	fputs(_(" -n, --dry-run              don't actually link anything\n"), out);
+	fputs(_(" -l, --list-duplicates      print every group of duplicate files\n"), out);
+	fputs(_(" -z, --zero                 delimit output with NULs instead of newlines\n"), out);
 	fputs(_(" -o, --ignore-owner         ignore owner changes\n"), out);
 	fputs(_(" -F, --prioritize-trees     files found in the earliest specified top-level\n"
                 "                              directory have higher priority (lower precedence\n"
@@ -1223,7 +1232,7 @@ static int parse_options(int argc, char *argv[])
 		OPT_REFLINK = CHAR_MAX + 1,
 		OPT_SKIP_RELINKS
 	};
-	static const char optstr[] = "VhvndfpotXcmMFOx:y:i:r:S:s:b:q";
+	static const char optstr[] = "VhvndfpotXcmMFOlzx:y:i:r:S:s:b:q";
 	static const struct option long_options[] = {
 		{"version", no_argument, NULL, 'V'},
 		{"help", no_argument, NULL, 'h'},
@@ -1252,6 +1261,8 @@ static int parse_options(int argc, char *argv[])
 		{"content", no_argument, NULL, 'c'},
 		{"quiet", no_argument, NULL, 'q'},
 		{"cache-size", required_argument, NULL, 'r'},
+		{"list-duplicates", no_argument, NULL, 'l'},
+		{"zero", no_argument, NULL, 'z'},
 		{NULL, 0, NULL, 0}
 	};
 	static const ul_excl_t excl[] = {
@@ -1329,6 +1340,14 @@ static int parse_options(int argc, char *argv[])
 		case 'b':
 			opts.io_size = strtosize_or_err(optarg, _("failed to parse I/O size"));
 			break;
+		case 'l':
+			opts.list_duplicates = TRUE;
+			opts.dry_run = TRUE;
+			quiet = TRUE;
+			break;
+		case 'z':
+			opts.line_delim = '\0';
+			break;
 #ifdef USE_REFLINK
 		case OPT_REFLINK:
 			reflink_mode = REFLINK_AUTO;
-- 
2.39.2
signature.asc (application/pgp-signature, 833 B)
-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEfWlHToQCjFzAxEFjvP0LAY0mWPEFAmcf1bIACgkQvP0LAY0m
WPGfDg/9FdqOIcjyDXL8iPOriiRbs6QAdNRQI53ehdwN+a5yFT95jAcX+3aabh97
8ne8fddjXrSMsUgrRWSd0bbRTqxt6oWGW3+9LrTv3JJv5FrjOPr5JhjG61JeTr2K
kqVvKy8jfXr61Huz2EuriXeuzUyFuGTZC3bTD/lejt4aoyF9PqnQ1jx95rrheAPp
LoB8kTrdoSGyyouddM8fNkBZOdDmN7Ak5jq8Q/QtDN1IsPPkF5iOKyxAJqiw7T5P
ld1k//x9R8Qwir15L0anIKVfUsY+JQyua/4QR2Y+iTFN/IWF/RppbBvLj/UwCh1P
zCShslVQ8IJT9GQcNi3NxHWSW0geKi8COeGaFMBYhsh8f23DeOaWFgk4zpXqJslp
TAZLPk60vzkQNrwfpoLPIZDzmwWI/uFjfmODZicKodL9PQuCWzHjitelY+Bp+MFb
b368qbhUsQB+RC/qaCIzJ/SYHD8Ca9HBZ6kXyla/4sYlaU8hONEQxJ3dsP0orfQC
8abnksOYNj+k2I/B8tP0/zFlR0fihkilC4b+yTdmgAni2+owBxEK6CrJKqyKdPIG
bNAy0a6l1WIX2QqsMN6LWSCjUAS4recDzOEFbj+nqI42LFyEdoWifk1UvTUwFaiI
s/SOZmpqeKpWpVHxYLqOsia0wP5vsJV/nOrvrXQjhjHlH4RoQAU=
=hh8f
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.