Why did mv delete a file with mv id_rsa *.old?
It has been renamed as known_hosts.old
, hence has overwritten the previous contents of known_hosts.old
.
As you already have a file named known_hosts.old
in there so the glob pattern *.old
has been expanded to known_hosts.old
.
In a nutshell, the following:
mv id_rsa *.old
has been expanded to:
mv id_rsa known_hosts.old
In bash
, if there was not a file named known_hosts.old
present there it would expand to literal *.old
(given you have not enabled nullglob
).
It looks like you thought that mv id_rsa *.old
would move id_rsa
to id_rsa.old
, with the *
replaced by the first argument, but this is not the case. Wildcards are expanded by the shell, not by the command. By the time mv
sees the command, the shell has expanded the wildcard. There are four cases:
- The wildcard pattern does not match any file. With most shells, this leaves the wildcard pattern unexpanded, and so
mv
is invoked with the argumentsid_rsa
and*.old
. It then movesid_rsa
to a file called*.old
(with the asterisk being the first character of the file name). Some shells (depending on their configuration) will instead display an error and not run the command in that case. - The wildcard pattern matches exactly one file which is not a directory. In this case, the shell replaces the pattern by the name of the matching file. Thus
mv
movesid_rsa
to that matching file, overwriting the previous file. This is what happened in your case:mv
was invoked with the argumentsid_rsa
andknown_hosts.old
, toknown_hosts.old
got overwritten. - The wildcard pattern matches two or more files, and the last one (in lexicographic order) is not a directory. In this case,
mv
complains, because all the files except the last one are source files, and it doesn't make sense to move multiple files onto the same file. - The wildcard pattern matches one or more file, and the last match (in lexicographic order) is a directory. The source file is moved into that directory. If there is already a file of the same name, it is overwritten. If the pattern has more than one match, this also applies to all the files matched by the pattern except the last one, since
mv
sees them as source files.
To avoid mv
unexpectedly overwriting target files, make it prompt for confirmation. Put this in your shell initialization (e.g. .bashrc
):
alias cp='cp -i'
alias mv='mv -i'
To rename a file based on its existing name, mv
alone is no help. You need to either use another tool, or arrange to provide mv
with the full destination name. One way to do what you were trying to do is with brace expansion, which lets you specify words with a common stem.
mv id_rsa{,.old}
The shell expands this to mv
with the arguments id_rsa
(id_rsa
concatenated with the empty string) and id_rsa.old
(id_rsa
concatenated with .old
).
To mass-rename files according to patterns, the most commonly useful tools are zmv
(zsh only), prename
and mmv
. To rename all files of the form from id_SOMETHING
to id_SOMETHING.old
, you can use
zmv 'id_*' '$f.old'
mmv 'id_*' 'id_#1.old'
prename 's/$//' id_*