Comment empêcher `mv` de déplacer une collection de fichiers en une seule régulière?

17

Je viens de perdre une petite partie de ma collection audio, par une stupide erreur que j'ai commise. :-(
GLADLY J'ai eu une sauvegarde assez récente, mais c'était toujours énervant. À part le vôtre vraiment, l'autre coupable était le méfait mv, ce qui se manifestera comme suit:

Les fichiers audio avaient un certain schéma:

ARTIST - Some Title YY.mp3

YYest la spécification de l'année à 2 chiffres.

mkdir 90<invisible control character>

(Jusqu'à ce moment, je ne savais pas que j'avais en fait tapé un tiers de caractère en excès qui était invisible ...!)
Au lieu d'avoir tout dans un répertoire, je voulais avoir toutes les musiques des années 90 dans un seul répertoire. J'ai donc tapé:

find . -name '* 9?.mp3' -exec mv {} 90 \;

Pas si difficile de savoir ce qui s'est passé hein? : ->
Le résultat (désastreux) était vierge vide répertoire appelé '90 quelque chose »(avec quelque chose étant le caractère de contrôle « invisible ») et un seul fichier appelé « 90 », écrasés n fois.

TOUS LES FICHIERS SONT DISPARUS. :-(( (évidemment)

Wish mvaurait vérifié à temps si la signature du "fichier" de destination (rappelez-vous sur * NIX: Everything Is A File ) commence par un d------(par exemple drwxr-xr-x). Et, bien sûr, si la destination existe . Il existe une variante du scénario ci - dessus, lorsque vous simplement oublié de mkdirle répertoire en premier. (mais bien sûr, vous supposiez que c'est là ...)

Même notre système d'exploitation pour animaux de compagnie, à commencer par la capitale, W LE FAIT. Vous êtes même invité à spécifier le type de destination (fichier? Répertoire?) Si vous le demandez.

Par conséquent, je me demande si nous * NIXers devons encore nous écrire un " mvscriptlet" juste pour éviter ce genre de surprises les plus indésirables.

erreur de syntaxe
la source
2
Tous les fichiers n'étaient pas partis. Au moins un .mp3devrait être là avec le nom 90, il aurait pu y en avoir un pour lequel vous n'aviez pas de sauvegarde.
Anthon
2
Hé, vous avez un sens de l'humour cynique, vous vous écrasez! :-P Eh bien, c'était le fichier appelé "un seul fichier" en gras dans mon OP. :)
erreur de syntaxe
2
mvn'est pas le problème ici, techniquement, il ne sait pas que vous déplacez une série de fichiers. Vous exécutez mvune seule fois pour chaque fichier. Voilà comment ça find -exec ;marche. Si vous aviez utilisé find -exec +(comme dans certains des commentaires) mv aurait crié dès qu'il a reçu plus d'un argument.
Etan Reisner
Bien que l'exécution mvde chaque fichier puisse sembler un peu moins réfléchie au premier abord, ce sera (comme je l'avais dit précédemment) la seule solution sensée une fois que les fichiers source seront dispersés dans divers sous-répertoires. Que dans mon cas de test, les fichiers sources soient tous dans un seul répertoire ne signifie pas que c'est mon cas de test réel . Il s'agit en fait d'une simple simplification, car je pourrai peut-être m'étendre sur ce sujet plus tard. De plus, la lecture des questions prend moins de temps en raison de leur longueur réduite. :)
erreur de syntaxe
Pourquoi vous attendriez-vous mvà exiger que la destination existe? mv oldfile newfileest le moyen de renommer un fichier, et il est stupide de s'attendre newfileà exister déjà et à être un répertoire.
Barmar

Réponses:

37

Vous pouvez ajouter un /à la destination si vous souhaitez déplacer des fichiers vers un répertoire. Dans le cas où le répertoire n'existe pas, vous recevrez une erreur:

mv somefile somedir/
mv: cannot move ‘somefile’ to ‘somedir/’: Not a directory

Dans le cas où le répertoire existe, il déplace le fichier dans ce répertoire.

Marco
la source
4
Merci beaucoup! Ce devrait être ma future bouée de sauvetage alors. Je t'en dois une. (Juste une note, un de mes amis avait fait la même erreur il y a quelques années, donc je sens que je ne suis pas seul.)
Syntaxerror
1
De plus, lorsque vous utilisez certains shells, vous avez l'option Tab pour compléter automatiquement les noms de fichiers pour vous. Si je ne me souviens pas très bien du nom du répertoire et que je ne veux pas un gâchis comme vous l'avez récemment fait, insérez simplement un caractère ou deux dans le nom du répertoire et HIT TAB . Ensuite, vous pouvez être sûr qu'il existe, car la
saisie semi
@AndyzSmith Eh bien, c'est la chose même. Vous pouvez appeler cela une de mes habitudes de n'utiliser TAB que pour des répertoires ou des chemins compliqués , mais pas pour ceux de type 2 lettres. :) Mais pensez-y ... peut-être que je devrais vraiment considérer ce dernier cas aussi à partir de maintenant.
syntaxerror
20

Le GNU coreutils a mvdéjà une option spécifiant que vous souhaitez déplacer vers un répertoire: -t/ --target-directory. Si l'argument de cette option n'existe pas, mvse plaindra au lieu de déplacer tous vos fichiers vers le même nom de fichier.

J'aurais écrit votre motionnaire comme suit:

find . -name '* 9?.mp3' -exec mv -t 90 {} +

Notez l'utilisation de +au lieu de \;, en regroupant autant de noms de fichiers que possible, ce qui accélère l'exécution.

Anthon
la source
Merci. (En espérant que ce n'est pas encore un de ces ismes GNU.)
erreur de syntaxe
2
@erreur de syntaxe. C'est un GNUisme. POSIXly:find . -name '* 9?.mp3' -exec sh -c 'exec mv "$@" 90/' sh {} +
Stéphane Chazelas
Merci beaucoup pour ce one-liner le plus SNEAKY! Pensez juste que c'était un +5 que je vous ai donné. :) rendra inutiles beaucoup d'essais et d'erreurs .--- Et vous savez trop bien pourquoi j'ai fait cette remarque. Juste besoin d'être sur une machine qui est directement POSIX (cela n'arrive pas trop rarement), et je vais avoir le prochain problème ici. :)
erreur de syntaxe
1
@syntaxerror Si cette réponse résout votre problème, veuillez prendre une minute et cliquez sur la coche sous le décompte des votes à gauche, cela signifiera à tout le monde que votre problème a été résolu et c'est la façon dont les remerciements sont exprimés sur le site. J'ai vu que seules quelques-unes de vos autres questions répondues ont accepté des réponses, vous voudrez peut-être les relire également.
Anthon
Non, c'est simplement un but. J'attendais souvent plusieurs semaines ou parfois 2 mois avant d'accepter une réponse. La raison en est que certaines personnes très bien informées ont un horaire TRÈS chargé et pourraient ne trouver le temps de donner leur (généralement la meilleure) réponse qu'après quelques semaines. Je trouve donc toujours respectueux d'attendre qu'ils s'arrêtent. Eh bien, et s'ils ne le font vraiment pas, je n'hésiterai pas à cocher la case, c'est sûr. D'ailleurs, je ne vois pas pourquoi certaines personnes sont toujours aussi pressées sur SE + ses saveurs. Facile, les gars. Ne sautez pas l'arme. :) Ce n'est pas votre patron qui vous presse.
erreur de syntaxe
10

De plus, si vous prévoyez généralement d'éviter les remplacements accidentels à l'avenir, il existe l' -ioption pour mv. Personnellement, je ne peux penser à aucun inconvénient si vous

alias mv='mv -i'

Si vous devez ensuite écraser quelque chose, passez simplement l' -foption.

Les alias ne prennent effet que si vous saisissez la commande directement dans un shell interactif, pas pour les cas comme l'appel par find. Tu aurais pu courir

find . -name '* 9?.mp3' -exec mv -i {} 90 \;

puis vous auriez été invité si vous mvaviez tenté de remplacer un fichier existant.

ayekat
la source
4
Ou - comme vous ne le saviez probablement pas - tapez «\ mv» au lieu de mv. Cette "astuce" moins connue fera en sorte que la commande avec la barre oblique inverse ajoutée à elle ignorera toutes les définitions d'alias.
erreur de syntaxe
Bien sûr, si vous parlez vraiment find ... -exec mv ..., alors vous devrez créer ~/bin/mv(ou un autre répertoire approprié) et le faire faire /bin/mv -i "$@"- car find ... -execne regarde pas les alias.
G-Man dit `` Réintègre Monica ''
Dans ce cas, je préfère env mv. Moins de frappe. :) Comme ma disposition de clavier locale nécessite que la touche MAJ soit enfoncée pour une barre oblique, je préférerais toujours les versions "sans barre oblique" (le cas échéant).
syntaxerror
1
@syntaxerror: BTW, lorsque vous répondez à un commentaire (dans un nouveau commentaire), il est courant de mentionner le nom de l'auteur, précédé de "@", comme dans "@ G-Man". De cette façon, je suis informé. (J'ai pu répondre à votre dernier commentaire de manière semi-rapide parce que j'ai été informé par le commentaire de Jenny D.) Vous pouvez abréger ou utiliser un nom complet (sans espaces), par exemple, «@ StéphaneChazelas». L'auteur d'un article est automatiquement informé des commentaires sur cet article. Voir les paragraphes Répondre dans les commentaires de cette page d'aide .
G-Man dit `` Réintègre Monica ''
1
Je suis désolé, j'étais sans Internet pendant une journée. @ G-Man Je conviens qu'avoir une version personnelle de mvdans un endroit au début $PATHserait une solution plus propre. D'un autre côté, il m'arrive principalement de négliger mvle shell interactif (car cela se produit rapidement). Au moment où je compose quelque chose de plus complexe, comme findou une forboucle, ou même un script shell, j'ai tendance à effectuer des essais à sec (en utilisant echo) pour m'assurer de ne pas casser quelque chose. Dans ces cas, je n'ai pas besoin de tenir une main mv, car j'y réfléchis déjà.
ayekat
7

En plus des excellentes réponses ci-dessus, j'aimerais clarifier pourquoi vous n'avez pas obtenu la question de savoir si déplacer les fichiers ou non.

Si vous déplacez un fichier vers un nouveau nom et que ce nom n'est pas un répertoire, mvrenommez votre fichier sous le nouveau nom.

Le problème ici est que vous utilisiez findpour exécuter mvune fois par fichier , pas une fois pour tous les fichiers .

Si, au lieu de cela, vous aviez fait mv *90.mp3 90, alors mvaurait échoué avec le message d'erreur que "le fichier cible n'est pas un répertoire".

Un autre conseil est d'utiliser la complétion de tabulation lors de la saisie du chemin cible. Il vous montrera si la cible est un répertoire en ajoutant /au nom de la cible. Vous pouvez également utiliser mv -ipour vous demander si vous souhaitez remplacer un fichier existant.

Jenny D
la source
Si, à la place, vous l'aviez fait mv *90.mp3 90, alors mv aurait échoué avec le message d'erreur que "le fichier cible n'est pas un répertoire". Hah, ouais, pourquoi si compliqué hein? J'utilise votre ligne et je serai heureux. Seulement dans ce cas trivial, cependant. :) Parce que c'est la façon normale de poser mes questions: je les ferais rétrécir par souci de simplicité. findJusqu'à présent, personne ne s'est opposé au fait que les fichiers des années 90 pourraient tout aussi bien être dispersés dans divers sous-répertoires que je souhaite également "capturer". Si et seulement s'ils sont toujours dans un répertoire source, votre mvligne est applicable.
erreur de syntaxe
@syntaxerror Très vrai - je voulais dire cela comme un exemple de la façon dont mvse comporte, pas comme une critique de votre choix d'outils.
Jenny D
1
Vous pourriez probablement construire quelque chose en combinant findet mv, par exemple find /music -type d -exec mv {}/*90.mp3 targetdir\;- mais maintenant je me sens un peu comme si je le compliquais trop, et simplement en utilisant -iou -test plus efficace
Jenny D
1
@Jenny: Si votre find . -type d -exec mv {}/*9?.mp3 target \;exemple fonctionnait, il y aurait toujours le risque que la mvcommande ressemble à mv file targetchaque répertoire contenant un seul *9?.mp3fichier; tous ces fichiers (sauf le dernier) seraient perdus.
G-Man dit `` Réintègre Monica ''
4
@syntaxerror: J'applaudis vos efforts pour exposer la partie essentielle de votre problème, plutôt que le script complet de 42 000 lignes dans lequel il se produit. Mais, même si vous vouliez faire quelque chose à tous les *.mp3fichiers d'une arborescence de répertoires, vous pourriez shopt -s globstaret ensuite exécuter votre commande **/*.mp3- le **va agir comme un find.
G-Man dit `` Réintègre Monica ''
1

En tant que stratégie alternative à usage général, je voudrais suggérer de transformer ce type d'opération en un script temporaire. Je préfère regarder les résultats de findet les transformer en mvcommande à la main, en m'assurant de bien comprendre ce que je fais avant d'exécuter. par exemple.

find . -name '* 9?.mp3' > tmp
vim tmp

Maintenant, je peux parcourir une liste de noms de fichiers et réécrire le contenu du fichier sous forme de commande shell.

  • Mettez le contenu du fichier sur une seule ligne: ggVGJ
  • Prepend: Imv [esc]
  • Ajouter: Asomedir/ [esc]
  • Enregistrez le fichier. Relisez-le. Respirez.
  • Exécutez source tmpsur la ligne de commande.

C'est une stratégie conservatrice, mais j'ai été mordu trop souvent par des commandes -execou des erreurs de frappe sed, ou par des extensions de shell mal comprises, et je préfère adopter une approche lente et cohérente.

En d'autres termes: je suis trop lâche pour utiliser -exec.

Tom Rees
la source
1
Vous avez omis l' :%s/.*/"&"/étape - parce que, dans ce cas, vous savez que chaque nom de fichier contient au moins un espace.
G-Man dit `` Réintègre Monica ''
1
Cela vous mordra si les noms de fichiers contiennent des espaces ou d'autres caractères spéciaux. Vous devez les citer correctement. La révision d'une liste de commandes n'est pas particulièrement susceptible de détecter des erreurs. Il existe de bien meilleures façons de passer en revue les commandes avant de les exécuter, telles que l'exécution echo mvau lieu de mv, puis la suppression de echosi vous êtes satisfait.
Gilles 'SO- arrête d'être méchant'
Repérer une erreur comme les noms de fichiers avec des espaces est précisément ce que cette technique aidera à :-)
Tom Rees
0

Une autre option:

-n, --no-clobber n'écrase pas un fichier existant

c'est la même chose que -i, mais ça ne demandera pas, ça échouera.

Jorge Nerín
la source