I accidentally forgot to specify destination before hitting the Return key. Where does
mv ./* without specifying destination move the files and directories under current directory to?
If the last argument was a directory, you just moved all of the files and directories in your current working directory (except those whose names begin with dots) into that directory. If there were two files, the first file may have overwritten the second file.
Here are some demonstrations:
More than two files and the last argument is a file
$ mkdir d1 d2 d3 $ touch a b c e $ mv * mv: target 'e' is not a directory
More than two files and the last argument is a directory
$ mkdir d1 d2 d3 $ touch a b c $ mv -v * 'a' -> 'd3/a' 'b' -> 'd3/b' 'c' -> 'd3/c' 'd1' -> 'd3/d1' 'd2' -> 'd3/d2'
$ touch a b $ mv -v * 'a' -> 'b'
The shell expands the glob (
*) into arguments for
mv. The glob is usually expanded in alphabetical order.
mv always sees a list of files and directories. It never sees the glob itself.
mv supports two types of moving. One is
mv file ... directory. The other is
mv old-file-name new-file-name (or
mv old-file-name directory/new-file-name).
First I'll make a test base - 5 files and one folder:
touch file1 file2 file3 file4 file5 mkdir folder
Next I'll run a test command. The
-v option specifies that I want every command the shell executes to be printed to
-x option specifies that I want the same printed to
stderr - but I want it done after the command is evaluated but before the shell runs it.
sh -cxv 'echo mv *'
echo mv * + echo mv file1 file2 file3 file4 file5 folder mv file1 file2 file3 file4 file5 folder
So you see that the command I feed the shell is
echo mv * and the command the shell executes after
* is expanded is
echo mv followed by all of those files and the folder.
By default the shell will expand globs like:
sh -cxv 'echo file[1-5]'
echo file[1-5] + echo file1 file2 file3 file4 file5 file1 file2 file3 file4 file5
This is a result of the
set [+-]f glob function:
sh -cxvf 'echo file[1-5]'
echo file[1-5] + echo 'file[1-5]' file[1-5]
So when you run a command in a shell configured with default options like
mv * the shell expands into the
* word an argument list of all files in the current directory sorted according to locale. It does the syscall
mv (essentially) with this argument list appended. So
mv gets all of the arguments as the shell globs them and sorts them. Besides doing
strace to see these effects, you can use the debug out again like:
sh -s -- mv * <<\SCRIPT sed -n l /proc/$$/cmdline echo "$@" SCRIPT
sh\000-s\000--\000mv\000file1\000file2\000file3\000file4\000file5\000folder\ \000$ mv file1 file2 file3 file4 file5 folder
( PS4= IFS=/; set -x mv *; : "/$*/" ) 2>&1
Basically, the shell executes
mv with the contents of the directory (if it is not empty and not including files/folders with names beginning with
.) as its argument list.
mv is POSIX specified to interpret its final argument as a directory if it is invoked with more than two arguments - in the same way
ln is (because, in fact, they're incredibly similar tools in underlying function).
sh -cxv 'mv *' ; ls
mv * + mv file1 file2 file3 file4 file5 folder folder/
All of the files were moved into the final argument - because it is a folder. Now what if it is not a folder?
sh -cxv 'cd *; mv *'; ls . *
cd *; mv * + cd folder + mv file1 file2 file3 file4 file5 mv: target ‘file5’ is not a directory .: folder/ folder: file1 file2 file3 file4 file5
This is how POSIX specifies
mv should behave in that case:
mv [-if] source_file target_file mv [-if] source_file... target_dir
In the first synopsis form, the
mvutility shall move the file named by the source_file operand to the destination specified by the target_file. This first synopsis form is assumed when the final operand does not name an existing directory and is not a symbolic link referring to an existing directory. In this case, if source_file names a non-directory file and target_file ends with a trailing
mvshall treat this as an error and no source_file operands will be processed.
In the second synopsis form,
mvshall move each file named by a source_file operand to a destination file in the existing directory named by the target_dir operand, or referenced if target_dir is a symbolic link referring to an existing directory. The destination path for each source_file shall be the concatenation of the target directory, a single
/slashcharacter if the target did not end in a
/slash, and the last pathname component of the source_file. This second form is assumed when the final operand names an existing directory.
* expands to:
- You should have only one file, which is the first
renamedto the second after the second is
- You should have only one file, which is the first
one or more files followed last by a directory or a link to one
- You should have only one directory or a link to one, which is where all its parent's previous contents have just been moved.
- You should have an error message and a satisfying sigh of relief.
First the shell expands
./* to all files in the current directory (except files starting with a dot).
- if there is no or only one file:
- if there are two file: the first one is moved to the second (which therefore get lost)
- if there are more than two files:
- if the last one is a directory: all files are moved into this directory
When you type
mv ./*, your shell will expand
./* before executing
A few things to note:
./*is expanded into less than 2 arguments,
mvwill, logically, produce an error.
./*will usually expand into every file (including directory) present in the current directory and not starting with a dot.
- You can control what
./*expands into by reading the documentation of your shell (
man 7 globis an entry point to the topic). Different shells will have different options.
Here's a shorter answer:
The shell expands the wildcard
* to a list of directory contents. Then the shell passes that full list to the command. The command never sees
mv file1 file2 ... filen directory will move file1 ... filen into directory.
Here I make a test directory containing three files
$ mkdir t $ cd t $ echo a>a; echo b>b; echo c>c $ ls a b c
You can't move multiple files into a single file
$ mv * mv: target `c' is not a directory
Let's add a subdirectory
$ mkdir d
You can move multiple files into a subdiretory
$ mv * $ ls d $ ls d a b c