web-dev-qa-db-fra.com

Renommer renvoie "le mot à nu non autorisé" lorsqu’on essaie de mettre en minuscule des parties de plusieurs noms de fichiers.

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

a1.dat
b1.DAT

Je souhaite renommer b1.DAT en b1.dat afin que les fichiers suivants apparaissent 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 cela n'a abouti à aucune solution significative ...

10
Samo

Cette erreur semble provenir de Perl renamename__. Vous devez utiliser la citation, mais vous devez uniquement spécifier la partie que vous souhaitez modifier, style de recherche et remplacement. La syntaxe est la suivante:

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

Supprimez -n après le test pour renommer les fichiers.

Pour inclure les 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 de nombreux chemins dans ou sous le répertoire en cours 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

Cela sera plus rapide si vos fichiers portent différents noms ne se terminant pas par .DAT.[1]

Pour désactiver ces paramètres, vous pouvez 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 donne une liste d'arguments excessivement longue, vous pouvez utiliser, par exemple, findname__:

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 de find ... -exec avec + est plus rapide que celle de \; car elle construit une liste d'arguments à partir des fichiers trouvés. Au départ, je pensais qu'il ne vous serait pas possible de l'utiliser, car vous avez mentionné que vous aviez un problème avec argument list too long, mais maintenant, je sais que la liste sera également intelligemment divisée en plusieurs invocations de la commande afin d'éviter ce problème. problème[2].

Étant donné que renametraitera tous les noms de fichiers de la même manière, la liste des arguments n'a pas d'importance, car elle peut être fractionnée en toute sécurité entre plusieurs invocations. Si la commande que vous utilisez avec -exec n'accepte pas plusieurs arguments ou exige que ses arguments soient placés dans un ordre particulier ou pour toute autre raison, le fait de scinder la liste d'arguments causera quelque chose d'inattendu, vous pouvez utiliser \;, ce qui entraîne l'exécution de la commande. invoqué une fois pour chaque fichier trouvé (si la liste des arguments était trop longue pour d'autres méthodes, cela prendrait beaucoup de temps!).


Merci beaucoup à Eliah Kagan pour les suggestions très utiles pour améliorer cette réponse:

[1] Spécifier les noms de fichiers lors de la suppression .
[2] find ... -exec avec + divise la liste des arguments .

11
Zanna

Tu peux faire:

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

Supprimez -n pour que le changement de nom ait lieu.

  • glob pattern *.DAT correspond à tous les fichiers se terminant par .DAT dans le répertoire en cours

  • dans la substitution de rename, DAT$ correspond à DAT à la fin

  • \L$& rend la correspondance complète en minuscule; $& fait référence à la correspondance complète

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)
6
heemayl

D'autresréponses ont abordé l'un des deux aspects principaux de cette question: celui de la manière de 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 "mot ouvert" est autorisé dans le contexte de la commande rename.

La première section de cette réponse concerne la relation entre rename et Perl et comment rename utilise le premier argument de ligne de commande que vous lui transmettez, à savoir son argument de code. La deuxième section traite de la manière dont le shell exécute des extensions - en particulier des globbing - pour construire une liste d'arguments. La troisième section traite de ce qui se passe dans le code Perl qui donne des erreurs "Bareword not allowed". Enfin, la quatrième section récapitule toutes les étapes entre la saisie de la commande et l'obtention de l'erreur.

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

Dans Debian et Ubuntu, la commande rename est un script Perl qui permet de renommer un fichier. Sur les versions antérieures - y compris 14.04 LTS, qui est toujours prise en charge à ce jour - c’était un lien symbolique pointant ( indirectement ) à la commande prename . Sur les nouvelles versions, il pointe à la commande plus récente file-rename . Ces deux commandes de changement de nom Perl fonctionnent généralement de la même manière, et je les nommerai toutes les deux par rename pour le reste de cette réponse.

Lorsque vous utilisez la commande rename, vous ne vous contentez pas d'exécuter du code Perl écrit par une autre personne. Vous écrivez également votre propre code Perl et indiquez à rename de l'exécuter. En effet, est le premier argument de ligne de commande que vous transmettez à la commande rename, à l'exception des arguments d'option tels que -n, est constitué du code Perl réel . La commande rename utilise ce code pour agir sur chacun des chemins d'accès que vous lui transmettez en tant qu'arguments de ligne de commande ultérieurs. (Si vous ne transmettez pas d'arguments de chemin d'accès, rename lit les noms de chemin d'accès à partir de l'entrée standard à la place, un par ligne.)

Le code est exécuté dans une boucle , qui itère une fois par chemin. Au début de chaque itération de la boucle, avant que votre code ne soit exécuté, la variable spéciale $_ se voit attribuer le chemin d'accès en cours de traitement. Si votre code entraîne que la valeur de $_ soit remplacée par autre chose, ce fichier est renommé avec le nouveau nom.

De nombreuses expressions de Perl opèrent de manière implicite sur la variable $_ lorsqu'aucune autre expression ne leur est attribuée en tant qu'opérande . Par exemple, l'expression de substitution $str =~ s/foo/bar/ modifie la première occurrence de foo dans la chaîne détenue par la variable $str 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 =~ , il opère alors sur $_. Cela signifie que s/foo/bar/ est l'abréviation de $_ =~ s/foo/bar/.

Il est courant de transmettre une expression s/// à rename en tant qu'argument de code (c'est-à-dire, le premier argument de ligne de commande), mais ce n'est pas obligatoire. Vous pouvez lui attribuer le code Perl que vous souhaitez exécuter dans la boucle afin d'examiner chaque valeur de $_ et de la modifier (de manière conditionnelle).

Cela a beaucoup de conséquences cool et utiles , mais la grande majorité d'entre elles vont bien au-delà de la portée de cette question et de cette réponse. La raison principale pour laquelle je soulève cette question - en fait, la raison principale pour laquelle j'ai décidé de poster cette réponse - est de préciser que, comme le premier argument de rename est en fait du code Perl, chaque fois que vous recevez un message d'erreur étrange et vous avez du mal à trouver des informations à ce sujet en cherchant, 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 commande rename n'a jamais vu *.DAT!

Une commande telle que rename s/foo/bar/ *.txt ne transmet généralement pas *.txt en tant qu'argument de ligne de commande au programme rename, et vous ne le souhaitez pas , sauf si vous avez un fichier dont le nom est littéralement *.txt, ce que vous n'espérez pas.

rename n'interprète pas les modèles globaux tels que *.txt, *.DAT, *.dat, x*, *y ou * lorsqu'ils lui sont transmis sous forme d'arguments de chemin d'accès. Au lieu de cela, votre Shell effectue l’extension du nom de chemin sur eux (ce qui est également appelé l’extension du nom de fichier et l’appellation globale). Cela se produit avant que l'utilitaire rename soit exécuté. Le shell développe les objets globaux en plusieurs noms de chemins potentiellement potentiels et les transmet tous, en tant qu'arguments de ligne de commande distincts, à rename. Dans Ubuntu, votre shell interactif est Bash , sauf si vous l'avez modifié. C'est pourquoi je me suis lié au manuel de référence Bash ci-dessus.

Il existe une situation dans laquelle un modèle global peut être transmis sous la forme d'un argument de ligne de commande non développé unique à 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 consiste simplement à transmettre le glob littéralement. Cependant, vous le voulez rarement! Si vous le voulez, vous devez assurer que le motif n'est pas développé, en le notant . Cela vaut pour passer des arguments à n’importe quelle commande, pas seulement à rename.

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

Le code Perl s/foo/bar/ ne contient aucun élément traité spécialement par Shell, mais cela aurait été une bonne idée pour moi de le citer lui aussi - et d'écrire 's/foo/bar/'. (En fait, la seule raison pour laquelle je ne l’ai pas fait, c’est que cela dérouterait certains lecteurs, car je n’avais pas encore parlé de citer.) La raison pour laquelle j’ai dit que cela aurait été bon est qu’il est très courant que Perl code ne == contient de tels caractères, et si je devais changer ce code, je ne me souviendrais peut-être pas de vérifier si des guillemets étaient nécessaires. En revanche, si vous souhaitez que le shell développe un objet global, il doit not être cité.

3. Ce que l'interprète Perl entend par "mot simple" n'est pas autorisé "

Les messages d'erreur que vous avez affichés dans votre question indiquent que, lorsque vous avez exécuté rename *.DAT *.dat, votre shell a développé *.DAT dans une liste d'un ou plusieurs noms de fichiers et que le en premier de ces noms de fichiers était b1.DAT. . Tous les arguments suivants - tous les autres développés à partir de *.DAT et tous ceux développés à partir de *.dat-- sont venus après cet argument, ils auraient donc été interprétés comme des noms de chemin d'accès.

Parce que ce qui était réellement exécuté était quelque chose comme rename b1.DAT ..., et parce que rename traitait son premier argument sans option comme un code Perl, la question est la suivante: pourquoi b1.DAT génère-t-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 des extensions non intentionnelles du shell qui les transformeraient automatiquement en d'autres chaînes (voir la section ci-dessus). . Les shells sont des langages de programmation spéciaux qui fonctionnent très différemment des langages généraux (et leur syntaxe très étrange et leur sémantique reflètent cela). Mais Perl est un langage de programmation général et, comme dans la plupart des langages de programmation généraux, le but principal de la citation en Perl n’est pas protect 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 ayez un chien, "votre chien" est une phrase de deux mots, alors que votre chien est un chien. De même, en Perl, '$foo' est une chaîne, alors que $foo est quelque chose dont le nom est $foo.

Cependant, contrairement à presque tous les autres langages de programmation d'usage général, Perl interprétera aussi == parfois le texte non mis entre guillemets comme étant mentionnant une chaîne-- une chaîne qui est la même que celle-ci, en ce sens qu'elle est composée des mêmes caractères dans le même ordre. Il essaiera seulement d'interpréter le code de cette façon si c'est un mot nu (pas de $ ou autre sigil, voir ci-dessous), et après cela, il n'a pas pu trouver d'autre signification à lui donner. . Ensuite, il le prendra sous forme de chaîne, sauf si vous ne le lui dites pas en activant des restrictions .

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

Les mots neutres servent à diverses fins, mais ils désignent généralement une fonction intégrée ou un sous-programme défini par l'utilisateur défini dans le programme (ou un module utilisé par le programme). Perl n'a pas de fonctions intégrées appelées b1 ou DAT. Ainsi, lorsque l'interpréteur Perl voit le code b1.DAT, il essaie de traiter b1 et DAT comme noms de sous-routines. En supposant qu'aucun de ces sous-programmes n'ait été défini, cela échoue. Ensuite, à condition que les restrictions n'aient pas été activées, il les traite comme des chaînes. Cela fonctionnera, même si quiconque destiné == cela est à deviner. L'opérateur . de Perl concatène des chaînes , de sorte que b1.DAT correspond à la chaîne b1DAT. C'est-à-dire que b1.DAT est une mauvaise façon d'écrire quelque chose comme 'b1' . 'DAT' ou "b1" . "DAT".

Vous pouvez le tester vous-même en exécutant la commande Perl -E 'say b1.DAT', qui transmet le script Perl court say b1.DAT à l'interpréteur Perl, qui l'exécute en imprimant b1DAT. (Dans cette commande, les guillemets '' indiquent au Shell de transmettre say b1.DAT sous la forme d'un argument de ligne de commande unique; sinon, l'espace ferait analyser say et b1.DAT sous forme de mots séparés et Perl les recevraient sous forme d'arguments séparés. Perl fait not voir les guillemets eux-mêmes, car le shell les supprime .

Mais maintenant, essayez d'écrire use strict;dans le script Perl avant say. Maintenant, cela échoue avec le même type d'erreur que vous avez eu avec 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 est dû au fait que use strict; a interdit à l'interpréteur Perl de traiter des mots nus comme des chaînes. Pour interdire cette fonctionnalité particulière, il serait vraiment suffisant d'activer uniquement la restriction subs. Cette commande produit les mêmes erreurs que ci-dessus:

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

Mais en général, les programmeurs Perl écrivent simplement use strict;, ce qui active la restriction subs et deux autres. use strict; est la pratique généralement recommandée. La commande rename effectue donc ceci 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 a transmis b1.DAT en tant que premier argument de ligne de commande, lequel rename a été traité en tant que code Perl à exécuter en boucle pour chaque argument de chemin d'accès.
  2. Cela a été interprété comme signifiant b1 et DAT connecté à l'opérateur ..
  3. b1 et DAT n'étaient pas précédés de sigils, ils ont donc été traités comme des mots nus.
  4. Ces deux mots nus auraient été pris comme noms de fonctions intégrées ou de sous-routines définies par l'utilisateur, mais aucun de ces noms n'avait de nom.
  5. Si les "subs strict" 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 "strict subs" a été activé, car rename permet toutes les restrictions (vars, refs et subs). Par conséquent, vous avez une erreur à la place.
  7. rename quitter en raison de cette erreur. Comme ce type d'erreur s'est produit tôt, aucune tentative de changement de nom de fichier n'a été tentée, même si vous n'aviez pas passé -n. C’est une bonne chose, qui protège souvent les utilisateurs des modifications de nom de fichier involontaires et parfois même de la perte de données.

Merci à Zanna, qui m'a aidé à combler plusieurs lacunes importantes dans une version plus antérieure de cette réponse . Sans elle, cette réponse aurait eu beaucoup moins de sens et pourrait ne pas être affichée du tout.

3
Eliah Kagan