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 arguments id_rsa and *.old. It then moves id_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 moves id_rsa to that matching file, overwriting the previous file. This is what happened in your case: mv was invoked with the arguments id_rsa and known_hosts.old, to known_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_*