I wanted to backup my ~/.ssh/id_rsa to id_rsa.old, and it looks like it got deleted! How is this possible? :)

root@localhost:~/.ssh# ls -l
total 16
-rw------- 1 root  root  3326 Mar 12 11:22 id_rsa
-rw-r--r-- 1 root  root   756 Mar 12 11:22 id_rsa.pub
-rw------- 1 userx userx  666 Mar  8 11:09 known_hosts
-rw-r--r-- 1 userx userx  666 Feb 29 10:53 known_hosts.old
root@localhost:~/.ssh# mv id_rsa *.old
root@localhost:~/.ssh# ls -l
total 12
-rw-r--r-- 1 root  root   756 Mar 12 11:22 id_rsa.pub
-rw------- 1 userx userx  666 Mar  8 11:09 known_hosts
-rw------- 1 root  root  3326 Mar 12 11:22 known_hosts.old
root@localhost:~/.ssh# touch p
root@localhost:~/.ssh# mv p *.p
root@localhost:~/.ssh# ls -l
total 12
-rw-r--r-- 1 root  root   756 Mar 12 11:22 id_rsa.pub
-rw------- 1 userx userx  666 Mar  8 11:09 known_hosts
-rw------- 1 root  root  3326 Mar 12 11:22 known_hosts.old
-rw-r--r-- 1 root  root     0 Mar 12 11:28 *.p
root@localhost:~/.ssh# rm *.p
root@localhost:~/.ssh# ls -l
total 12
-rw-r--r-- 1 root  root   756 Mar 12 11:22 id_rsa.pub
-rw------- 1 userx userx  666 Mar  8 11:09 known_hosts
-rw------- 1 root  root  3326 Mar 12 11:22 known_hosts.old
userx@localhost:~$ uname -r
userx@localhost:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 15.10
Release:    15.10
Codename:   wily
userx@localhost:~$ bash --version
GNU bash, version 4.3.42(1)-release (x86_64-pc-linux-gnu)
  • 4
    @Scott How on Earth is this a duplicate of that?! This question is about move re move'ing a file. The answer may be similar, but this is a COMPLETELY different question. – Tersosauros Mar 14 '16 at 5:40
  • 3
    Actually, it IS a duplicate because the underlying problem is the same - you expected unix filename globs to work exactly the same dos/windows wildcards. They don't. Furthermore, you don't see it as a duplicate because you're expecting to be given a single magic command that will solve your exact problem without you having to think or understand, rather than an explanation of what the problem is and generic information on how to solve it. Another unrealistic/false expectation. – cas Mar 14 '16 at 10:42
  • 1
    Look carefully at the filesizes. id_rsa was 3326 and known_hosts.old was 666. After the move, id_rsa is gone, but known_hosts.old is 3326. – Joshua Taylor Mar 14 '16 at 13:23
  • @cas: "Duplicate" and "underlying problem are the same" are completely different issues. It's very possible for multiple questions to have the same underlying problem; this does not invalidate their status as independent questions. In this case, you're wrongly comparing a question from a user who understands how unix globs work but wants to do something like MSDOS wildcards with a question from a user who mistakenly thinks globs are MSDOS wildcards. – R.. GitHub STOP HELPING ICE Mar 14 '16 at 18:15
  • Thanks for discovering the obvious flaw in my mental circuitry - thinking that MSDOS rules apply to bash. How silly of me :) I will mark it as duplicate! – Bojan Landekić Mar 14 '16 at 20:27

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).

  • 2
    In the output that's shown, this appears very quickly with the filesize. Note that id_rsa had size 3326 while known_hosts.old had 666. After the move, id_rsa is gone, and known_hosts.old is 3326. – Joshua Taylor Mar 14 '16 at 13:22

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_*
  • Also repren... – Deer Hunter Mar 14 '16 at 5:06
  • 2
    In your last case (wildcard expands to one or more elements, the last being a directory), then wouldn't the source file and the others that match the expansion be moved into the directory? – TripeHound Mar 14 '16 at 10:33
  • 1
    ITYM most Bourne-like shells; the Thomson shell, csh, tcsh, zsh, fish don't leave the pattern unexpanded as it would be a silly thing to do (something broken by the Bourne shell) – Stéphane Chazelas Mar 14 '16 at 12:49
  • 1
    So, the Bourne shell (and bash) are singularly responsible for people saying find . -name *.doc … — it blows up in other shells (except for the unlucky case where the glob matches one file in the current directory).  I wonder why Bourne thought that was a good idea. – Scott Mar 14 '16 at 13:47
  • 1
    @BojanLandekić The problem is that find . -name *.doc in Bourne/POSIX shells means different things depending on the number of files that match the pattern *.doc in the current directory: if there is none, it's equivalent to find . -name '*.doc'; if there is exactly one, e.g. foo.doc, it's equivalent to find . -name foo.doc; and if there's more than one and no weird file names then find will complain of a syntax error. – Gilles 'SO- stop being evil' Mar 14 '16 at 21:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.