Que fait `mv ./*` sans spécifier de destination?

27

J'ai accidentellement oublié de spécifier la destination avant d'appuyer sur la touche Retour. Où, mv ./*sans spécifier de destination, déplace-t-il les fichiers et les répertoires du répertoire actuel?

Tim
la source
4
"Je suis surpris que mvj'accepte un argument" Ce n'est pas le cas. Il accepte tous les arguments que le shell lui a transmis après avoir développé le *.
glglgl

Réponses:

41

Si le dernier argument était un répertoire, vous venez de déplacer tous les fichiers et répertoires de votre répertoire de travail actuel (sauf ceux dont les noms commencent par des points) dans ce répertoire. S'il y avait deux fichiers, le premier fichier peut avoir écrasé le deuxième fichier.

Voici quelques démonstrations:

Plus de deux fichiers et le dernier argument est un fichier

$ mkdir d1 d2 d3
$ touch a b c e
$ mv *
mv: target 'e' is not a directory

Plus de deux fichiers et le dernier argument est un répertoire

$ 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'

Deux fichiers

$ touch a b
$ mv -v *
'a' -> 'b'

Plus d'explications

Le shell développe le glob ( *) en arguments pour mv. Le glob est généralement développé par ordre alphabétique. mvvoit toujours une liste de fichiers et de répertoires. Il ne voit jamais le globe lui-même.

La commande mvprend en charge deux types de déplacement. L'un est mv file ... directory. L'autre est mv old-file-name new-file-name(ou mv old-file-name directory/new-file-name).

terdon
la source
30

Je vais d'abord faire une base de test - 5 fichiers et un dossier:

touch file1 file2 file3 file4 file5
mkdir folder

Ensuite, je vais exécuter une commande de test. L' -voption spécifie que je veux que chaque commande exécutée par le shell soit imprimée stderr. L' -xoption spécifie que je veux que le même soit imprimé stderr- mais je veux que ce soit fait après que la commande soit évaluée mais avant que le shell ne l'exécute.

sh -cxv 'echo mv *'

SORTIE

echo mv *
+ echo mv file1 file2 file3 file4 file5 folder
mv file1 file2 file3 file4 file5 folder

Vous voyez donc que la commande que j'alimente le shell est echo mv *et que la commande que le shell exécute après avoir * été développée est echo mvsuivie par tous ces fichiers et le dossier.

Par défaut, le shell étendra les globes comme:

sh -cxv 'echo file[1-5]'

SORTIE

echo file[1-5]
+ echo file1 file2 file3 file4 file5
file1 file2 file3 file4 file5

Ceci est le résultat de la set [+-]ffonction glob:

sh -cxvf 'echo file[1-5]'

SORTIE

echo file[1-5]
+ echo 'file[1-5]'
file[1-5]

Ainsi, lorsque vous exécutez une commande dans un shell configuré avec des options par défaut comme mv *le shell se développe dans le *mot une liste d'arguments de tous les fichiers du répertoire en cours triés en fonction des paramètres régionaux. Il effectue l'appel système exec(ve)pour mv (essentiellement) avec cette liste d'arguments ajoutée. Obtient donc mvtous les arguments lorsque le shell les globule et les trie. En plus de faire stracepour voir ces effets, vous pouvez utiliser à nouveau le débogage comme:

sh -s -- mv * <<\SCRIPT
sed -n l /proc/$$/cmdline
echo "$@"
SCRIPT

SORTIE

sh\000-s\000--\000mv\000file1\000file2\000file3\000file4\000file5\000folder\
\000$
mv file1 file2 file3 file4 file5 folder

Et de façon portative:

( PS4= IFS=/; set -x mv *; : "/$*/" ) 2>&1

SORTIE

: /mv/file1/file2/file3/file4/file5/folder/

Fondamentalement, le shell s'exécute mvavec le contenu du répertoire (s'il n'est pas vide et n'inclut pas les fichiers / dossiers commençant par .) comme liste d'arguments. mvest spécifié pour interpréter Posix son dernier argument en tant que répertoire si elle est invoquée avec plus de deux arguments - de la même manière lnest (parce que, en fait, ils sont des outils incroyablement similaires dans la fonction sous - jacente) .

Assez echocependant:

sh -cxv 'mv *' ; ls

SORTIE

mv *
+ mv file1 file2 file3 file4 file5 folder
folder/

Tous les fichiers ont été déplacés dans l'argument final, car il s'agit d'un dossier. Et si ce n'est pas un dossier?

sh -cxv 'cd *; mv *'; ls . *

SORTIE

cd *; mv *
+ cd folder
+ mv file1 file2 file3 file4 file5
mv: target ‘file5’ is not a directory

.:
folder/

folder:
file1  file2  file3  file4  file5

Voici comment POSIX mv doit se comporter dans ce cas:

mv [-if] source_file target_file

mv [-if] source_file... target_dir

Dans le premier formulaire de synopsis, l' mvutilitaire doit déplacer le fichier nommé par l'opérande source_file vers la destination spécifiée par target_file . Cette première forme de synopsis est supposée lorsque l'opérande final ne nomme pas de répertoire existant et n'est pas un lien symbolique faisant référence à un répertoire existant. Dans ce cas, si source_file nomme un fichier non-répertoire et target_file se termine par un /slashcaractère de fin , mvdoit traiter cela comme une erreur et aucun opérande source_file ne sera traité.

Dans le deuxième formulaire de synopsis, mvdoit déplacer chaque fichier nommé par un opérande source_file vers un fichier de destination dans le répertoire existant nommé par l' opérande target_dir , ou référencé si target_dir est un lien symbolique faisant référence à un répertoire existant. Le chemin de destination pour chaque fichier source doit être la concaténation du répertoire cible, un seul /slashcaractère si la cible ne s'est pas terminée par un /slashet le dernier composant de chemin d'accès du fichier source . Cette seconde forme est supposée lorsque l'opérande final nomme un répertoire existant.

Donc, si *étend à:

  • deux fichiers

    • Vous ne devriez avoir qu'un seul fichier, qui est le premier renamedau second après le second unlinked.
  • un ou plusieurs fichiers suivis en dernier par un répertoire ou un lien vers un

    • Vous ne devriez avoir qu'un seul répertoire ou un lien vers un, c'est là que tout le contenu précédent de son parent vient d'être déplacé.
  • rien d'autre

    • Vous devriez avoir un message d'erreur et un soupir de soulagement satisfaisant.
mikeserv
la source
1
oui, en utilisant le vieux shmachin, j'ai oublié de déboguer avec ça, mais en ce qui concerne ma question d'origine, cela signifie que le problème n'est pas le shell mv? C'est méchant, c'est plus simple que je ne le pensais à l'origine mais c'est un vrai piège.
user2485710
5
@ user2485710, le point clé est que sous Unix, le shell est responsable de l'expansion des caractères génériques avant de passer les arguments de la ligne de commande à la commande. Sous Windows, chaque commande doit développer elle-même les caractères génériques. Cela permet aux shells Unix d'offrir des fonctionnalités avancées de caractères génériques qui fonctionnent avec n'importe quelle commande.
cjm
2
@mikeserv étant donné que ces fichiers n'étaient pas si importants, je me suis précipité pour nettoyer et passer à l'étape suivante, mais je peux confirmer les choses telles qu'elles apparaissent maintenant dans ma modification de mon premier message / question.
user2485710
6
@mikeserv tl; dr, *n'est-ce pas un seul argument? Droite? :)
Bernhard
1
@Bernhard - en fait, c'est le seul cas que je n'ai pas couvert - car il se pourrait .
mikeserv
18

Tout d'abord, le shell s'étend ./*à tous les fichiers du répertoire courant (à l'exception des fichiers commençant par un point).

  • s'il n'y a pas ou un seul fichier: mvéchoue
  • s'il y a deux fichiers: le premier est déplacé vers le second (qui se perd donc)
  • s'il y a plus de deux fichiers:
    • si le dernier est un répertoire: tous les fichiers sont déplacés dans ce répertoire
    • sinon mvéchoue.
jofel
la source
1
Merci. comment savoir quel sous-répertoire est "le dernier"?
Tim
7
Il suffit d'appeler echo ./*pour voir l'ordre utilisé par votre shell (généralement alphabétique).
jofel du
S'il échoue avec un fichier, pourquoi ne le dit-il pas?
Noumenon
@Noumenon Je ne comprends pas votre commentaire. mvrenvoie un message d'erreur s'il est appelé avec un seul argument.
jofel
Vous avez raison. La même commande de mon histoire donne maintenant, missing destination file operand after *filename*même si hier ce n'était pas le cas.
Noumenon
4

Lorsque vous tapez mv ./*, votre shell se développe ./*avant de s'exécuter mv.

Quelques points à noter:

  • Si ./*est développé en moins de 2 arguments, cela mvproduira logiquement une erreur.
  • ./* sera généralement développé dans chaque fichier (y compris le répertoire) présent dans le répertoire actuel et ne commençant pas par un point.
  • Vous pouvez contrôler ce qui se ./*développe en lisant la documentation de votre shell ( man 7 globest un point d'entrée vers la rubrique). Différents obus auront différentes options.
rahmu
la source
1

Que fait mv *-il?

Voici une réponse plus courte:

Le shell étend le caractère générique *à une liste de contenu de répertoire. Le shell transmet ensuite cette liste complète à la commande. La commande ne voit jamais *.

La commande mv file1 file2 ... filen directorydéplacera file1 ... filen dans le répertoire.

Exemple

Ici, je fais un répertoire de test contenant trois fichiers

$ mkdir t
$ cd t
$ echo a>a; echo b>b; echo c>c
$ ls
a  b  c

Vous ne pouvez pas déplacer plusieurs fichiers dans un seul fichier

$ mv *
mv: target `c' is not a directory

Ajoutons un sous-répertoire

$ mkdir d

Vous pouvez déplacer plusieurs fichiers dans un sous-répertoire

$ mv *
$ ls
d
$ ls d
a  b  c
RedGrittyBrick
la source
Il mv file1 file2 ... filen directoryest très peu probable que la commande ait quoi que ce soit à voir *.
mikeserv
@Mike: Ma réponse souligne que la dernière étape de l' expansion du shell transforme efficacement ce dernier en premier. Ne pas savoir que cela semble être à l'origine de la confusion du PO.
RedGrittyBrick
oui, mais mon point est la coquille ne serait pas étendre *à file dirmoins qu'il y ait lieu dans lequel dsuit f.
mikeserv
@mikeserv: Il existe un tel environnement local, mon exemple est un copier-coller de Putty se connectant à un système CentOS 5.6 GNU / Linux.
RedGrittyBrick
quel endroit est-ce? votre exemple est a b c dqui ne l'est pas file ... dir. J'ai seulement commenté parce que vous ne mentionnez le genre nulle part et dites seulement que cela *devient file ... dirce qui ne se produit pas.
mikeserv