1.mv介绍
用法:./mv [选项]... [-T] 源文件 目标文件
或:./mv [选项]... 源文件... 目录
或:./mv [选项]... -t 目录 源文件...
将源文件重命名为目标文件,或将源文件移动至指定目录。
长选项必须使用的参数对于短选项时也是必需使用的。
--backup[=CONTROL] 为每个已存在的目标文件创建备份
-b 类似--backup 但不接受参数
-f, --force 覆盖前不询问
-i, --interactive 覆盖前询问
-n, --no-clobber 不覆盖已存在文件
如果您指定了-i、-f、-n 中的多个,仅最后一个生效。
--strip-trailing-slashes 去掉每个源文件参数尾部的斜线
-S, --suffix=SUFFIX 替换常用的备份文件后缀
-t, --target-directory=DIRECTORY 将所有参数指定的源文件或目录
移动至 指定目录
-T, --no-target-directory 将目标文件视作普通文件处理
-u, --update 只在源文件文件比目标文件新,或目标文件
不存在时才进行移动
-v, --verbose 详细显示进行的步骤
--help 显示此帮助信息并退出
--version 显示版本信息并退出
mv [选项] 源文件或目录 目标文件或目录
视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中。当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名。当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中。在跨文件系统移动文件时,mv先拷贝,再将原有文件删除,而链至该文件的链接也将丢失。
参 数:
-b或--backup 若需覆盖文件,则覆盖前先行备份。
-f或--force 若目标文件或目录与现有的文件或目录重复,则直接覆盖现有的文 件或目录。
-i或--interactive 覆盖前先行询问用户。
-S<附加字尾>或
--suffix=<附加字尾> 与-b参数一并使用,可指定备份文件的所要附加的字尾。
-u或--update 在移动或更改文件名时,若目标文件已存在,且其文件日期比源文件新,则不覆盖目标文件。
-v或--verbose 执行时显示详细的信息。
-V=<方法>或
--version-control=<方法> 与-b参数一并使用,可指定备份的方法。
--help 显示帮助。
--version 显示版本信息。
2.
代码就是用来实现以上行为的。加了注释的代码如下:
rm_options
struct rm_options
{
/* If true, ignore nonexistent files. */
bool ignore_missing_files;
/* If true, query the user about whether to remove each file. */
enum rm_interactive interactive;
/* If true, do not traverse into (or remove) any directory that is
on a file system (i.e., that has a different device number) other
than that of the corresponding command line argument. Note that
even without this option, rm will fail in the end, due to its
probable inability to remove the mount point. But there, the
diagnostic comes too late -- after removing all contents. */
bool one_file_system;
/* If true, recursively remove directories. */
bool recursive;
/* Pointer to the device and inode numbers of `/', when --recursive
and preserving `/'. Otherwise NULL. */
struct dev_ino *root_dev_ino;
/* If nonzero, stdin is a tty. */
bool stdin_tty;
/* If true, display the name of each file removed. */
bool verbose;
/* If true, treat the failure by the rm function to restore the
current working directory as a fatal error. I.e., if this field
is true and the rm function cannot restore cwd, it must exit with
a nonzero status. Some applications require that the rm function
restore cwd (e.g., mv) and some others do not (e.g., rm,
in many cases). */
bool require_restore_cwd;
};
cp_options
/* These options control how files are copied by at least the
following programs: mv (when rename doesn't work), cp, install.
So, if you add a new member, be sure to initialize it in
mv.c, cp.c, and install.c. */
struct cp_options
{
enum backup_type backup_type;
/* How to handle symlinks in the source. */
enum Dereference_symlink dereference;
/* This value is used to determine whether to prompt before removing
each existing destination file. It works differently depending on
whether move_mode is set. See code/comments in copy.c. */
enum Interactive interactive;
/* Control creation of sparse files. */
enum Sparse_type sparse_mode;
/* Set the mode of the destination file to exactly this value
if SET_MODE is nonzero. */
mode_t mode;
/* If true, copy all files except (directories and, if not dereferencing
them, symbolic links,) as if they were regular files. */
bool copy_as_regular;
/* If true, remove each existing destination nondirectory before
trying to open it. */
bool unlink_dest_before_opening;
/* If true, first try to open each existing destination nondirectory,
then, if the open fails, unlink and try again.
This option must be set for `cp -f', in case the destination file
exists when the open is attempted. It is irrelevant to `mv' since
any destination is sure to be removed before the open. */
bool unlink_dest_after_failed_open;
/* If true, create hard links instead of copying files.
Create destination directories as usual. */
bool hard_link;
/* If true, rather than copying, first attempt to use rename.
If that fails, then resort to copying. */
bool move_mode;
/* Whether this process has appropriate privileges to chown a file
whose owner is not the effective user ID. */
bool chown_privileges;
/* Whether this process has appropriate privileges to do the
following operations on a file even when it is owned by some
other user: set the file's atime, mtime, mode, or ACL; remove or
rename an entry in the file even though it is a sticky directory,
or to mount on the file. */
bool owner_privileges;
/* If true, when copying recursively, skip any subdirectories that are
on different file systems from the one we started on. */
bool one_file_system;
/* If true, attempt to give the copies the original files' permissions,
owner, group, and timestamps. */
bool preserve_ownership;
bool preserve_mode;
bool preserve_timestamps;
/* Enabled for mv, and for cp by the --preserve=links option.
If true, attempt to preserve in the destination files any
logical hard links between the source files. If used with cp's
--no-dereference option, and copying two hard-linked files,
the two corresponding destination files will also be hard linked.
If used with cp's --dereference (-L) option, then, as that option implies,
hard links are *not* preserved. However, when copying a file F and
a symlink S to F, the resulting S and F in the destination directory
will be hard links to the same file (a copy of F). */
bool preserve_links;
/* If true and any of the above (for preserve) file attributes cannot
be applied to a destination file, treat it as a failure and return
nonzero immediately. E.g. for cp -p this must be true, for mv it
must be false. */
bool require_preserve;
/* If true, attempt to preserve the SELinux security context, too.
Set this only if the kernel is SELinux enabled. */
bool preserve_security_context;
/* Useful only when preserve_security_context is true.
If true, a failed attempt to preserve a file's security context
propagates failure "out" to the caller. If false, a failure to
preserve a file's security context does not change the invoking
application's exit status. Give diagnostics for failed syscalls
regardless of this setting. For example, with "cp --preserve=context"
this flag is "true", while with "cp -a", it is false. That means
"cp -a" attempts to preserve any security context, but does not
fail if it is unable to do so. */
bool require_preserve_context;
/* If true, attempt to preserve extended attributes using libattr.
Ignored if coreutils are compiled without xattr support. */
bool preserve_xattr;
/* Useful only when preserve_xattr is true.
If true, a failed attempt to preserve file's extended attributes
propagates failure "out" to the caller. If false, a failure to
preserve file's extended attributes does not change the invoking
application's exit status. Give diagnostics for failed syscalls
regardless of this setting. For example, with "cp --preserve=xattr"
this flag is "true", while with "cp --preserve=all", it is false. */
bool require_preserve_xattr;
/* Used as difference boolean between cp -a and cp -dR --preserve=all.
If true, non-mandatory failure diagnostics are not displayed. This
should prevent poluting cp -a output.
*/
bool reduce_diagnostics;
/* If true, copy directories recursively and copy special files
as themselves rather than copying their contents. */
bool recursive;
/* If true, set file mode to value of MODE. Otherwise,
set it based on current umask modified by UMASK_KILL. */
bool set_mode;
/* If true, create symbolic links instead of copying files.
Create destination directories as usual. */
bool symbolic_link;
/* If true, do not copy a nondirectory that has an existing destination
with the same or newer modification time. */
bool update;
/* If true, display the names of the files before copying them. */
bool verbose;
/* If true, stdin is a tty. */
bool stdin_tty;
/* If true, open a dangling destination symlink when not in move_mode.
Otherwise, copy_reg gives a diagnostic (it refuses to write through
such a symlink) and returns false. */
bool open_dangling_dest_symlink;
/* This is a set of destination name/inode/dev triples. Each such triple
represents a file we have created corresponding to a source file name
that was specified on the command line. Use it to avoid clobbering
source files in commands like this:
rm -rf a b c; mkdir a b c; touch a/f b/f; mv a/f b/f c
For now, it protects only regular files when copying (i.e. not renaming).
When renaming, it protects all non-directories.
Use dest_info_init to initialize it, or set it to NULL to disable
this feature. */
Hash_table *dest_info;
/* FIXME */
Hash_table *src_info;
};
mv.c
/* mv -- move or rename files
Copyright (C) 86, 89, 90, 91, 1995-2009 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* Written by Mike Parker, David MacKenzie, and Jim Meyering */
#include <config.h>
#include <stdio.h>
#include <getopt.h>
#include <sys/types.h>
#include <assert.h>
#include <selinux/selinux.h>
#include "system.h"
#include "backupfile.h"
#include "copy.h"
#include "cp-hash.h"
#include "error.h"
#include "filenamecat.h"
#include "quote.h"
#include "remove.h"
#include "root-dev-ino.h"
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "mv"
#define AUTHORS \
proper_name ("Mike Parker"), \
proper_name ("David MacKenzie"), \
proper_name ("Jim Meyering")
/* Initial number of entries in each hash table entry's table of inodes. */
#define INITIAL_HASH_MODULE 100
/* Initial number of entries in the inode hash table. */
#define INITIAL_ENTRY_TAB_SIZE 70
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1
};
/* Remove any trailing slashes from each SOURCE argument. */
static bool remove_trailing_slashes;
static struct option const long_options[] =
{
{"backup", optional_argument, NULL, 'b'},
{"force", no_argument, NULL, 'f'},
{"interactive", no_argument, NULL, 'i'},
{"no-clobber", no_argument, NULL, 'n'},
{"no-target-directory", no_argument, NULL, 'T'},
{"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION},
{"suffix", required_argument, NULL, 'S'},
{"target-directory", required_argument, NULL, 't'},
{"update", no_argument, NULL, 'u'},
{"verbose", no_argument, NULL, 'v'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
};
//初始化rm_options结构体
static void
rm_option_init (struct rm_options *x)
{
x->ignore_missing_files = false;
x->recursive = true;
x->one_file_system = false;
/* Should we prompt for removal, too? No. Prompting for the `move'
part is enough. It implies removal. */
x->interactive = RMI_NEVER;
x->stdin_tty = false;
x->verbose = false;
/* Since this program may well have to process additional command
line arguments after any call to `rm', that function must preserve
the initial working directory, in case one of those is a
`.'-relative name. */
x->require_restore_cwd = true;
{
static struct dev_ino dev_ino_buf;
x->root_dev_ino = get_root_dev_ino (&dev_ino_buf);
if (x->root_dev_ino == NULL)
error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
quote ("/"));
}
}
//初始化cp_options结构体
static void
cp_option_init (struct cp_options *x)
{
bool selinux_enabled = (0 < is_selinux_enabled ());
cp_options_default (x);
x->copy_as_regular = false; /* FIXME: maybe make this an option */
x->dereference = DEREF_NEVER;
x->unlink_dest_before_opening = false;
x->unlink_dest_after_failed_open = false;
x->hard_link = false;
x->interactive = I_UNSPECIFIED;
x->move_mode = true;
x->one_file_system = false;
x->preserve_ownership = true;
x->preserve_links = true;
x->preserve_mode = true;
x->preserve_timestamps = true;
x->preserve_security_context = selinux_enabled;
x->reduce_diagnostics = false;
x->require_preserve = false; /* FIXME: maybe make this an option */
x->require_preserve_context = false;
x->preserve_xattr = true;
x->require_preserve_xattr = false;
x->recursive = true;
x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */
x->symbolic_link = false;
x->set_mode = false;
x->mode = 0;
x->stdin_tty = isatty (STDIN_FILENO);
x->open_dangling_dest_symlink = false;
x->update = false;
x->verbose = false;
x->dest_info = NULL;
x->src_info = NULL;
}
/* FILE is the last operand of this command. Return true if FILE is a
directory. But report an error if there is a problem accessing FILE, other
than nonexistence (errno == ENOENT). */
//判断是否文件夹
static bool
target_directory_operand (char const *file)
{
struct stat st;
int err = (stat (file, &st) == 0 ? 0 : errno);
bool is_a_dir = !err && S_ISDIR (st.st_mode);
if (err && err != ENOENT)
error (EXIT_FAILURE, err, _("accessing %s"), quote (file));
return is_a_dir;
}
/* Move SOURCE onto DEST. Handles cross-file-system moves.
If SOURCE is a directory, DEST must not exist.
Return true if successful. */
static bool
do_move (const char *source, const char *dest, const struct cp_options *x)
{
bool copy_into_self;
bool rename_succeeded;
bool ok = copy (source, dest, false, x, ©_into_self, &rename_succeeded);
if (ok)
{
char const *dir_to_remove;
if (copy_into_self)
{
/* In general, when copy returns with copy_into_self set, SOURCE is
the same as, or a parent of DEST. In this case we know it's a
parent. It doesn't make sense to move a directory into itself, and
besides in some situations doing so would give highly nonintuitive
results. Run this `mkdir b; touch a c; mv * b' in an empty
directory. Here's the result of running echo `find b -print`:
b b/a b/b b/b/a b/c. Notice that only file `a' was copied
into b/b. Handle this by giving a diagnostic, removing the
copied-into-self directory, DEST (`b/b' in the example),
and failing. */
dir_to_remove = NULL;
ok = false;
}
else if (rename_succeeded)
{
/* No need to remove anything. SOURCE was successfully
renamed to DEST. Or the user declined to rename a file. */
dir_to_remove = NULL;
}
else
{
/* This may mean SOURCE and DEST referred to different devices.
It may also conceivably mean that even though they referred
to the same device, rename wasn't implemented for that device.
E.g., (from Joel N. Weber),
[...] there might someday be cases where you can't rename
but you can copy where the device name is the same, especially
on Hurd. Consider an ftpfs with a primitive ftp server that
supports uploading, downloading and deleting, but not renaming.
Also, note that comparing device numbers is not a reliable
check for `can-rename'. Some systems can be set up so that
files from many different physical devices all have the same
st_dev field. This is a feature of some NFS mounting
configurations.
We reach this point if SOURCE has been successfully copied
to DEST. Now we have to remove SOURCE.
This function used to resort to copying only when rename
failed and set errno to EXDEV. */
dir_to_remove = source;
}
if (dir_to_remove != NULL)
{
struct rm_options rm_options;
enum RM_status status;
rm_option_init (&rm_options);
rm_options.verbose = x->verbose;
status = rm (1, &dir_to_remove, &rm_options);
assert (VALID_STATUS (status));
if (status == RM_ERROR)
ok = false;
}
}
return ok;
}
/* Move file SOURCE onto DEST. Handles the case when DEST is a directory.
Treat DEST as a directory if DEST_IS_DIR.
Return true if successful. */
static bool
movefile (char *source, char *dest, bool dest_is_dir,
const struct cp_options *x)
{
bool ok;
/* This code was introduced to handle the ambiguity in the semantics
of mv that is induced by the varying semantics of the rename function.
Some systems (e.g., Linux) have a rename function that honors a
trailing slash, while others (like Solaris 5,6,7) have a rename
function that ignores a trailing slash. I believe the Linux
rename semantics are POSIX and susv2 compliant. */
if (remove_trailing_slashes)
strip_trailing_slashes (source);
if (dest_is_dir)
{
/* Treat DEST as a directory; build the full filename. */
char const *src_basename = last_component (source);
char *new_dest = file_name_concat (dest, src_basename, NULL); //递归调用中使用了malloc函数。
strip_trailing_slashes (new_dest);
ok = do_move (source, new_dest, x);
free (new_dest);
}
else
{
ok = do_move (source, dest, x);
}
return ok;
}
void
usage (int status)
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [OPTION]... [-T] SOURCE DEST\n\
or: %s [OPTION]... SOURCE... DIRECTORY\n\
or: %s [OPTION]... -t DIRECTORY SOURCE...\n\
"),
program_name, program_name, program_name);
fputs (_("\
Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
\n\
"), stdout);
fputs (_("\
Mandatory arguments to long options are mandatory for short options too.\n\
"), stdout);
fputs (_("\
--backup[=CONTROL] make a backup of each existing destination file\n\
-b like --backup but does not accept an argument\n\
-f, --force do not prompt before overwriting\n\
-i, --interactive prompt before overwrite\n\
-n, --no-clobber do not overwrite an existing file\n\
If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
"), stdout);
fputs (_("\
--strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
argument\n\
-S, --suffix=SUFFIX override the usual backup suffix\n\
"), stdout);
fputs (_("\
-t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\
-T, --no-target-directory treat DEST as a normal file\n\
-u, --update move only when the SOURCE file is newer\n\
than the destination file or when the\n\
destination file is missing\n\
-v, --verbose explain what is being done\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
The backup suffix is `~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\
The version control method may be selected via the --backup option or through\n\
the VERSION_CONTROL environment variable. Here are the values:\n\
\n\
"), stdout);
fputs (_("\
none, off never make backups (even if --backup is given)\n\
numbered, t make numbered backups\n\
existing, nil numbered if numbered backups exist, simple otherwise\n\
simple, never always make simple backups\n\
"), stdout);
emit_bug_reporting_address ();
}
exit (status);
}
int
main (int argc, char **argv)
{
printf("--%s\n","hello");
int c;
bool ok;
bool make_backups = false;
char *backup_suffix_string;
char *version_control_string = NULL;
struct cp_options x;
char *target_directory = NULL;
bool no_target_directory = false;
int n_files;
char **file;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdin);
cp_option_init (&x);
/* FIXME: consider not calling getenv for SIMPLE_BACKUP_SUFFIX unless
we'll actually use backup_suffix_string. */
backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
while ((c = getopt_long (argc, argv, "bfint:uvS:T", long_options, NULL))
!= -1)
{
switch (c)
{
case 'b':
make_backups = true;
if (optarg)
version_control_string = optarg;
break;
case 'f':
x.interactive = I_ALWAYS_YES;
break;
case 'i':
x.interactive = I_ASK_USER;
break;
case 'n':
x.interactive = I_ALWAYS_NO;
break;
case STRIP_TRAILING_SLASHES_OPTION:
remove_trailing_slashes = true;
break;
case 't': //颠倒作用,mv source target(必须为文件夹) ==mv -t target(必须为文件夹) source
if (target_directory)
error (EXIT_FAILURE, 0, _("multiple target directories specified"));
else //执行这里,上面的分支应该不会执行(从代码中看)
{
struct stat st;
if (stat (optarg, &st) != 0)
error (EXIT_FAILURE, errno, _("accessing %s"), quote (optarg));
if (! S_ISDIR (st.st_mode))
error (EXIT_FAILURE, 0, _("target %s is not a directory"),
quote (optarg));
}
target_directory = optarg;
break;
case 'T':
no_target_directory = true;
break;
case 'u':
x.update = true;
break;
case 'v':
x.verbose = true;
break;
case 'S':
make_backups = true;
backup_suffix_string = optarg;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
usage (EXIT_FAILURE);
}
}
n_files = argc - optind;
file = argv + optind; //为file赋值。
if (n_files <= !target_directory)
{
if (n_files <= 0)
error (0, 0, _("missing file operand"));
else
error (0, 0, _("missing destination file operand after %s"),
quote (file[0]));
usage (EXIT_FAILURE);
}
if (no_target_directory) //加-T了
{
if (target_directory)
error (EXIT_FAILURE, 0,
_("cannot combine --target-directory (-t) "
"and --no-target-directory (-T)"));
if (2 < n_files)
{
error (0, 0, _("extra operand %s"), quote (file[2]));
usage (EXIT_FAILURE);
}
}
else if (!target_directory) //不加-t时执行这里,一般使用mv没加-t
{
assert (2 <= n_files);
if (target_directory_operand (file[n_files - 1]))
target_directory = file[--n_files];
else if (2 < n_files)
error (EXIT_FAILURE, 0, _("target %s is not a directory"),
quote (file[n_files - 1]));
}
if (make_backups && x.interactive == I_ALWAYS_NO)
{
error (0, 0,
_("options --backup and --no-clobber are mutually exclusive"));
usage (EXIT_FAILURE);
}
if (backup_suffix_string)
simple_backup_suffix = xstrdup (backup_suffix_string);
x.backup_type = (make_backups
? xget_version (_("backup type"),
version_control_string)
: no_backups);
hash_init ();
if (target_directory) //加了-t
{
int i;
/* Initialize the hash table only if we'll need it.
The problem it is used to detect can arise only if there are
two or more files to move. */
if (2 <= n_files)
dest_info_init (&x);
ok = true;
for (i = 0; i < n_files; ++i)
ok &= movefile (file[i], target_directory, true, &x);
}
else
ok = movefile (file[0], file[1], false, &x);
exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
}
3.
已了解了mv.c的流程,一些细节如quote函数等等需要进一步观察。

浙公网安备 33010602011771号