Renommer renvoie «bareword non autorisé» lors de la tentative de minuscules des parties de plusieurs noms de fichiers

12

J'ai deux fichiers dans un dossier sur mon Ubuntu 16.04:

a1.dat
b1.DAT

Je veux renommer b1.DATpour b1.datque j'aie les fichiers suivants comme résultat dans le dossier:

a1.dat
b1.dat

J'ai essayé (sans succès):

$ rename *.DAT *.dat
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

et

$ find . -iname "*.DAT" -exec rename DAT dat '{}' \;
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

La recherche de ce résultat n'a abouti à aucune solution significative ...

Samo
la source
Vous pourriez aussi vouloir en savoir plus sur mmv
PlasmaHH

Réponses:

14

Cette erreur semble provenir de Perl rename. Vous devez utiliser des guillemets, mais il vous suffit de spécifier la pièce que vous souhaitez modifier, le style de recherche et de remplacement. La syntaxe est la suivante:

rename -n 's/\.DAT/\.dat/' *

Supprimez -naprès le test pour renommer réellement les fichiers.

Pour inclure des fichiers cachés, modifiez le paramètre glob avant d'exécuter la commande:

shopt -s dotglob

Si vous souhaitez renommer des fichiers de manière récursive, vous pouvez utiliser

shopt -s globstar
rename -n 's/\.DAT/\.dat/' **

ou s'il y a beaucoup de chemins dans ou en dessous du répertoire courant qui ne se terminent pas par .DAT, vous feriez mieux de spécifier ces chemins dans la deuxième commande:

rename -n 's/\.DAT/\.dat/' **/*.DAT

Ce sera plus rapide si vos fichiers ont des noms différents qui ne se terminent pas par .DAT. [1]

Pour désactiver ces paramètres, vous pouvez les utiliser shopt -u, par exemple shopt -u globstar, mais ils sont désactivés par défaut et le seront lorsque vous ouvrirez un nouveau shell.

Si cela aboutit à une liste d'arguments excessivement longue, vous pouvez utiliser, par exemple find,:

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} \;

ou mieux

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} +

L'utilisation find ... -execavec +est plus rapide que l'utilisation \;car elle construit une liste d'arguments à partir des fichiers trouvés. À l'origine, je pensais qu'il ne serait pas possible pour vous de l'utiliser, car vous avez mentionné que vous rencontriez un argument list too longproblème, mais maintenant je sais que la liste sera également astucieusement divisée en plusieurs invocations de la commande selon les besoins pour éviter ce problème. [2] .

Étant donné renameque traitera chaque nom de fichier de la même manière, la longueur de la liste d'arguments n'a pas d'importance car elle peut être divisée en toute sécurité sur plusieurs appels. Si la commande avec laquelle vous utilisez -execn'accepte pas plusieurs arguments ou requiert que ses arguments soient dans un ordre particulier ou pour toute autre raison, le fractionnement de la liste d'arguments entraînera quelque chose de indésirable, vous pouvez utiliser \;, ce qui provoque l'invocation de la commande une fois pour chaque fichier trouvé (si la liste des arguments était trop longue pour d'autres méthodes, cela prendra du temps!).


Un grand merci à Eliah Kagan pour les suggestions très utiles pour améliorer cette réponse:

[1] Spécification des noms de fichiers lors de la globalisation .
[2] find ... -execavec +divise la liste des arguments .

Zanna
la source
Merci. Comment le faire récursivement (pour tous les fichiers de tous les sous-dossiers)?
Samo
@Samo voir mon montage :)
Zanna
Ne fonctionne pas: $ rename 's / \. DAT / \. Dat /' ** bash: / usr / bin / rename: Liste d'arguments trop longue
Samo
1
@Samo vous devez supprimer le -nde renommer après le test - c'est juste pour montrer ce qui sera changé
Zanna
1
Sur Centos et Arch, renommer ne présente pas ce comportement. Je suis arrivé ici en utilisant Debian sur WSL. Cette syntaxe a fonctionné.
xtian
6

Tu peux faire:

rename -n 's/DAT$/\L$&/' *.DAT

Supprimer -npour que le changement de nom ait lieu.

  • le modèle glob *.DATcorrespond à tous les fichiers se terminant .DATdans le répertoire courant

  • dans la renamesubstitution de, DAT$matchs DATà la fin

  • \L$&rend le match entier en minuscules; $&fait référence à l'ensemble du match

Si vous voulez juste faire pour b1.DAT:

rename -n 's/DAT$/\L$&/' b1.DAT

Exemple:

% rename -n 's/DAT$/\L$&/' *.DAT
rename(b1.DAT, b1.dat)
heemayl
la source
4

D'autres réponses ont abordé l'un des deux principaux aspects de cette question: celui de savoir comment mener à bien l'opération de changement de nom dont vous aviez besoin. Le but de cette réponse est d'expliquer pourquoi vos commandes n'ont pas fonctionné, y compris la signification de cet étrange message d'erreur "bareword not allowed" dans le contexte de la renamecommande.

La première section de cette réponse concerne la relation entre renameet Perl et comment renameutilise le premier argument de ligne de commande que vous lui transmettez, qui est son argument de code. La deuxième section concerne la façon dont le shell effectue des extensions - en particulier le globbing - pour construire une liste d'arguments. La troisième section concerne ce qui se passe dans le code Perl qui donne des erreurs «Bareword not allowed». Enfin, la quatrième section est un résumé de toutes les étapes qui se déroulent entre la saisie de la commande et l'obtention de l'erreur.

1. Lorsque renamevous donne des messages d'erreur étranges, ajoutez "Perl" à votre recherche.

Dans Debian et Ubuntu, la renamecommande est un script Perl qui effectue un changement de nom de fichier. Sur les versions antérieures - y compris 14.04 LTS, qui est toujours pris en charge au moment de la rédaction de ce document - c'était un lien symbolique pointant ( indirectement ) vers la prenamecommande. Sur les versions plus récentes, il pointe à la place sur la nouvelle file-renamecommande. Ces deux commandes de changement de nom Perl fonctionnent principalement de la même manière, et je vais me référer à elles deux comme renamepour le reste de cette réponse.

Lorsque vous utilisez la renamecommande, vous n'exécutez pas uniquement du code Perl que quelqu'un d'autre a écrit. Vous écrivez également votre propre code Perl et dites renamede l'exécuter. Cela est dû au fait que le premier argument de ligne de commande que vous passez à la renamecommande, autre que les arguments d'option comme -n, se compose de code Perl réel . La renamecommande utilise ce code pour opérer sur chacun des chemins d'accès que vous lui transmettez comme arguments de ligne de commande ultérieurs. (Si vous ne passez aucun argument de chemin d'accès, il renamelit alors les noms de chemin à partir de l'entrée standard , un par ligne.)

Le code est exécuté dans une boucle , qui itère une fois par nom de chemin. En haut de chaque itération de la boucle, avant l'exécution de votre code, la $_variable spéciale se voit attribuer le chemin d'accès en cours de traitement. Si votre code entraîne la $_modification de la valeur de quelque chose d'autre, alors ce fichier est renommé pour avoir le nouveau nom.

De nombreuses expressions en Perl opèrent implicitement sur la $_variable lorsqu'aucune autre expression ne leur est utilisée comme opérande . Par exemple, l'expression de substitution $str =~ s/foo/bar/change la première occurrence de foodans la chaîne contenue par la $str variable en bar, ou la laisse inchangée si elle ne contient pas foo. Si vous écrivez simplement s/foo/bar/sans utiliser explicitement l' =~opérateur , alors il fonctionne $_. C'est-à-dire que s/foo/bar/c'est court pour $_ =~ s/foo/bar/.

Il est courant de passer une s///expression à renamecomme argument de code (le premier argument de ligne de commande), mais vous ne devez pas. Vous pouvez lui donner n'importe quel code Perl que vous souhaitez qu'il s'exécute à l'intérieur de la boucle, pour examiner chaque valeur de $_et (conditionnellement) le modifier.

Cela a beaucoup de conséquences intéressantes et utiles , mais la grande majorité d'entre elles dépassent largement le cadre de cette question et réponse. La principale raison pour laquelle j'apporte ceci ici - en fait, la principale raison pour laquelle j'ai décidé de poster cette réponse - est de faire valoir que, parce que le premier argument renameest en fait du code Perl, chaque fois que vous obtenez un message d'erreur étrange et que vous avoir du mal à trouver des informations à ce sujet en recherchant, vous pouvez ajouter "Perl" à la chaîne de recherche (ou même remplacer "renommer" par "Perl", parfois) et vous trouverez souvent une réponse.

2. Avec rename *.DAT *.dat, la renamecommande n'a jamais vu *.DAT!

Une commande comme rename s/foo/bar/ *.txtne passe généralement pas en *.txttant qu'argument de ligne de commande au renameprogramme, et vous ne le souhaitez pas , sauf si vous avez un fichier dont le nom est littéralement *.txt, ce qui, espérons-le, ne vous convient pas.

renamene pas interpréter les modèles de glob aiment *.txt, *.DAT, *.dat, x*, *you *lorsqu'il lui a été transmis comme arguments pathname. Au lieu de cela, votre shell effectue une expansion de nom de chemin sur eux (ce qui est également appelé expansion de nom de fichier et également appelé globbing). Cela se produit avant l' renameexécution de l' utilitaire. Le shell développe les globs en potentiellement plusieurs chemins d'accès et les transmet tous, en tant qu'arguments de ligne de commande séparés, à rename. Dans Ubuntu, votre shell interactif est Bash , à moins que vous ne l'ayez modifié, c'est pourquoi je me suis connecté au manuel de référence Bash ci-dessus.

Il existe une situation dans laquelle un modèle glob peut être transmis sous la forme d'un seul argument de ligne de commande non développé à rename: lorsqu'il ne correspond à aucun fichier. Différents shells présentent un comportement par défaut différent dans cette situation, mais le comportement par défaut de Bash est de simplement passer le glob littéralement. Cependant, vous en avez rarement envie! Si vous le vouliez, vous devez vous assurer que le modèle n'est pas développé, en le citant . Cela vaut pour passer des arguments à n'importe quelle commande, pas seulement à rename.

Les citations ne sont pas uniquement destinées à la globalisation (extension de nom de fichier), car il existe d' autres extensions que votre shell effectue sur du texte non cité et, pour certaines d'entre elles mais pas pour d'autres , également sur du texte entre " "guillemets. En général, chaque fois que vous souhaitez passer un argument contenant des caractères qui peuvent être traités spécialement par le shell, y compris des espaces, vous devez le citer, de préférence avec des ' 'guillemets .

Le code Perl s/foo/bar/ne contient rien de traité spécialement par le shell, mais cela aurait été une bonne idée pour moi de l'avoir aussi cité - et écrit 's/foo/bar/'. (En fait, la seule raison pour laquelle je ne l' ai pas été qu'il serait source de confusion pour certains lecteurs, comme je l' avais pas encore parlé de citation.) La raison pour laquelle je dis cela aurait été bon est parce qu'il est très fréquent que le code Perl ne contient ces caractères, et si je devais changer ce code, je ne me souviendrais peut-être pas de vérifier si la citation était nécessaire. En revanche, si vous souhaitez que le shell développe un glob, il ne doit pas être cité.

3. Ce que l'interprète Perl veut dire par "mots nus non autorisés"

Les messages d'erreur que vous avez montrés dans votre question révèlent que, lorsque vous avez exécuté rename *.DAT *.dat, votre shell s'est développé *.DATen une liste d'un ou plusieurs noms de fichiers, et que le premier de ces noms de fichiers l'était b1.DAT. Tous les arguments suivants - tous les autres à partir de *.DATet tous ceux à partir de - ont été *.datdéveloppés après cet argument, ils auraient donc été interprétés comme des chemins d'accès.

Parce que ce qui s'est réellement exécuté était quelque chose comme rename b1.DAT ..., et parce que renametraite son premier argument sans option en tant que code Perl, la question devient: pourquoi b1.DATproduit-il ces erreurs "bareword not allowed" lorsque vous l'exécutez en tant que code Perl?

Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Dans un shell, nous citons nos chaînes pour les protéger contre les extensions de shell involontaires qui autrement les transformeraient automatiquement en d'autres chaînes (voir la section ci-dessus). Les shells sont des langages de programmation à usage spécial qui fonctionnent très différemment des langages à usage général (et leur syntaxe et sémantique très étranges le reflètent). Mais Perl est un langage de programmation à usage général et, comme la plupart des langages de programmation à usage général, le but principal de la citation en Perl n'est pas de protéger les chaînes, mais de les mentionner du tout. C'est en fait une façon dont la plupart des langages de programmation sont similaires au langage naturel. En anglais, et en supposant que vous avez un chien, "votre chien" est une phrase de deux mots, tandis que votre chien est un chien. De même, en Perl, '$foo'est une chaîne, tandis que $fooquelque chose dont le nom est $foo.

Cependant, contrairement à peu près tous les autres langages de programmation à usage général, Perl aussi interpréter parfois le texte sans guillemets en mentionnant une chaîne - une chaîne qui est le « même » comme, dans le sens où il est composé des mêmes caractères le même ordre. Il essaiera d'interpréter le code de cette façon uniquement s'il s'agit d'un mot simple (aucun $ou autre sigil, voir ci-dessous), et après il n'a pas pu trouver d'autre sens à lui donner. Ensuite, il le prendra comme une chaîne, sauf si vous le lui dites en activant les restrictions .

Les variables en Perl commencent généralement par un caractère de ponctuation appelé sigil , qui spécifie le type large de la variable. Par exemple, $signifie scalaire , @signifie tableau et %signifie hachage . ( Il y en a d'autres. ) Ne vous inquiétez pas si vous trouvez cela déroutant (ou ennuyeux), car je ne fais que le dire pour dire que lorsqu'un nom valide apparaît dans un programme Perl mais n'est pas précédé d'un sigil, ce nom est dit être un simple mot .

Les mots nus servent à diverses fins, mais ils signifient généralement une fonction intégrée ou un sous-programme défini par l' utilisateur qui a été défini dans le programme (ou dans un module utilisé par le programme). Perl n'a pas de fonctions intégrées appelées b1ou DAT, donc quand l'interpréteur Perl voit le code b1.DAT, il essaie de traiter b1et DATcomme les noms des sous-programmes. En supposant qu'aucun de ces sous-programmes n'a été défini, cela échoue. Ensuite, à condition que les restrictions n'aient pas été activées, il les traite comme des chaînes. Cela fonctionne, si vous avez réellement si oui ou non l' intention que cela se produise est quiconque conjecture. L' .opérateur de Perl concatène les chaînes , donc b1.DATévalue la chaîneb1DAT. Autrement dit, b1.DATest une mauvaise façon d'écrire quelque chose comme 'b1' . 'DAT'ou "b1" . "DAT".

Vous pouvez tester cela vous-même en exécutant la commande perl -E 'say b1.DAT', qui transmet le court script Perl say b1.DATà l'interpréteur Perl, qui l'exécute, en imprimant b1DAT. (Dans cette commande, les ' 'citations indiquent la coquille de passer say b1.DATcomme un seul argument de ligne de commande, sinon, l'espace causerait sayet b1.DATà analysable comme des mots séparés et perlles recevait comme arguments distincts. perlNe pas voir les citations elles - mêmes, la shell les supprime .)

Mais maintenant, essayez d'écrireuse strict; dans le script Perl avant say. Maintenant, il échoue avec le même type d'erreur que vous avez obtenu rename:

$ perl -E 'use strict; say b1.DAT'
Bareword "b1" not allowed while "strict subs" in use at -e line 1.
Bareword "DAT" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.

Cela s'est produit parce que use strict;l'interpréteur Perl ne pouvait pas traiter les mots nus comme des chaînes. Pour interdire cette fonctionnalité particulière, il suffirait vraiment d'activer uniquement la subsrestriction. Cette commande produit les mêmes erreurs que ci-dessus:

perl -E 'use strict "subs"; say b1.DAT'

Mais généralement, les programmeurs Perl écrivent use strict;, ce qui active la subsrestriction et deux autres. use strict;est une pratique généralement recommandée. La renamecommande fait donc cela pour votre code. C'est pourquoi vous obtenez ce message d'erreur.

4. En résumé, voici ce qui s'est passé:

  1. Votre shell est passé en b1.DATtant que premier argument de ligne de commande, renametraité comme du code Perl à exécuter en boucle pour chaque argument de chemin d'accès.
  2. Cela a été pris pour signifier b1et DATlié à l' .opérateur.
  3. b1et DATn'étaient pas préfixés par des sceaux, ils étaient donc traités comme des mots nus.
  4. Ces deux mots nus auraient été considérés comme des noms de fonctions intégrées ou de sous-programmes définis par l'utilisateur, mais rien de tel n'avait l'un de ces noms.
  5. Si les "sous-marins stricts" n'avaient pas été activés, ils auraient été traités comme les expressions de chaîne 'b1'et 'DAT'et concaténés. C'est loin de ce que vous vouliez, ce qui montre à quel point cette fonctionnalité n'est souvent pas utile.
  6. Mais « sous - marins stricts » a été activé, car renamepermet toutes les restrictions ( vars, refset subs). Par conséquent, vous avez plutôt reçu une erreur.
  7. renamequitter en raison de cette erreur. Étant donné que ce type d'erreur s'est produit tôt, aucune tentative de changement de nom n'a été effectuée, même si vous ne l'avez pas réussi -n. C'est une bonne chose, qui protège souvent les utilisateurs contre les changements involontaires de nom de fichier et parfois même contre la perte de données réelle.

Merci à Zanna , qui m'a aidé à combler plusieurs lacunes importantes dans une version plus récente de cette réponse . Sans elle, cette réponse aurait été beaucoup moins logique et n'aurait peut-être pas été publiée du tout.

Eliah Kagan
la source