logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 57a2fea900fc5e056629101639d0045aea4881d4
parent d0d1c4fafa929ff4b4f5ae435947c727d8b8fdb6
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat, 13 Dec 2025 17:17:30 +0100

cmd/mv: add support for -T option

Diffstat:

Mcmd/mv.17+++++--
Mcmd/mv.c39+++++++++++++++++++++++++++++++++++++--
Mtest-cmd/mv.t8++++++++
3 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/cmd/mv.1 b/cmd/mv.1 @@ -10,7 +10,7 @@ .Sh SYNOPSIS .Nm .Op Fl f Ns | Ns Fl i Ns | Ns Fl n -.Op Fl v +.Op Fl Tv .Ar source .Ar destfile .Nm @@ -75,6 +75,8 @@ and options. .It Fl t Ar destdir Set the destination directory. +.It Fl T +Always treat destination as a file. .It Fl v Verbose, write which action has been done. .El @@ -88,7 +90,8 @@ specification. .Pp The .Fl n , -.Fl t Ar destdir +.Fl t Ar destdir , +.Fl T , and .Fl v options are extensions. diff --git a/cmd/mv.c b/cmd/mv.c @@ -43,6 +43,7 @@ const char *argv0 = "mv"; bool no_clob = false, force = false, interact = false, verbose = false; +bool opt_T = false; static int stdin_tty = 0; @@ -347,6 +348,13 @@ do_renameat(struct named_fd srcdir, break; case EISDIR: case ENOTDIR: + if(opt_T) + { + fprintf( + stderr, "mv: error: Failed moving '%s' to '%s': %s\n", src, dest, strerror(errno)); + return -1; + } + if(destdir.fd != AT_FDCWD) { fprintf( @@ -428,15 +436,16 @@ main(int argc, char *argv[]) {"interactive", no_argument, NULL, 'i'}, {"no-clobber", no_argument, NULL, 'n'}, {"target-directory", required_argument, NULL, 't'}, + {"no-target-directory", no_argument, NULL, 'T'}, {"verbose", no_argument, NULL, 'v'}, {0, 0, 0, 0}, }; // clang-format on // Need + as first character to get POSIX-style option parsing - for(int c = -1; (c = getopt_long(argc, argv, "+:fint:v", opts, NULL)) != -1;) + for(int c = -1; (c = getopt_long(argc, argv, "+:fint:Tv", opts, NULL)) != -1;) #else - for(int c = -1; (c = getopt_nolong(argc, argv, ":fint:v")) != -1;) + for(int c = -1; (c = getopt_nolong(argc, argv, ":fint:Tv")) != -1;) #endif { switch(c) @@ -470,6 +479,9 @@ main(int argc, char *argv[]) return 1; } break; + case 'T': + opt_T = true; + break; case 'v': verbose = true; break; @@ -502,6 +514,21 @@ main(int argc, char *argv[]) stdin_tty = isatty(STDIN_FILENO); if(!stdin_tty) errno = 0; + if(opt_T) + { + if(destdir.fd != AT_FDCWD) + { + fprintf(stderr, "mv: error: Cannot pass both -t and -T\n"); + return 1; + } + + if(argc != 2) + { + fprintf(stderr, "mv: error: Option -T passed but got %d arguments instead of 2\n", argc); + return 1; + } + } + if(destdir.fd == AT_FDCWD) { if(argc <= 1) @@ -510,6 +537,14 @@ main(int argc, char *argv[]) return 1; } + if(opt_T) + { + int ret = do_renameat(srcdir, argv[0], destdir, argv[1]); + + consent_finish(); + return ret < 0 ? 1 : 0; + } + struct stat dest_status; int ret_stat = fstatat(destdir.fd, argv[1], &dest_status, 0); if( diff --git a/test-cmd/mv.t b/test-cmd/mv.t @@ -150,6 +150,14 @@ Works with non-resolving source symlinks $ test -L symlink_enoent.done $ rm symlink_enoent.done +Option -T + $ mkdir opt_T.d + $ touch opt_T + $ mv -T opt_T opt_T.d + mv: error: Failed moving 'opt_T' to 'opt_T.d': Is a directory + [1] + $ rm -fr opt_T opt_T.d + No files should be left $ find . .