Pourquoi les noms de mes dossiers se sont-ils retrouvés ainsi et comment puis-je résoudre ce problème à l'aide d'un script?

15

Désolé si cela a une réponse ailleurs, je ne sais pas comment rechercher mon problème.

J'exécutais des simulations sur un serveur HPC Linux redhat, et mon code pour gérer la structure des dossiers pour enregistrer la sortie avait un bug malheureux. Mon code matlab pour créer le dossier était:

folder = [sp.saveLocation, 'run_', sp.run_number, '/'];

sp.run_numberétait un entier. J'ai oublié de le convertir en chaîne, mais pour une raison quelconque, l'exécution mkdir(folder);(dans matlab) a quand même réussi. En fait, les simulations se sont déroulées sans accroc et les données ont été enregistrées dans le répertoire correspondant.

Maintenant, lorsque la structure du dossier est interrogée / imprimée, j'obtiens les situations suivantes:

  • Lorsque j'essaie de tabuler la saisie semi-automatique: run_ run_^A/ run_^B/ run_^C/ run_^D/ run_^E/ run_^F/ run_^G/ run_^H/ run_^I/
  • Quand je l' utilise ls: run_ run_? run_? run_? run_? run_? run_? run_? run_? run_? run_?.
  • Lorsque je transfère sur mon mac à l'aide de rsync, l' --progressoption affiche: run_\#003/etc. avec (je suppose) le nombre correspondant à l'entier en sp.run_numbercapitonné à trois chiffres, donc la 10e exécution estrun_\#010/
  • Lorsque je regarde les dossiers dans le Finder, je vois run_ run_ run_ run_ run_ run_ run_ run_ run_ run_?
  • En regardant cette question et en utilisant la commande ls | LC_ALL=C sed -n lque j'obtiens:
run_$
run_\001$
run_\002$
run_\003$
run_\004$
run_\005$
run_\006$
run_\a$
run_\b$
run_\t$
run_$

Je ne parviens pas à cdaccéder aux dossiers à l'aide de ces représentations.

J'ai des milliers de ces dossiers, je vais donc devoir corriger cela avec un script. Laquelle de ces options est la représentation correcte du dossier? Comment puis-je faire référence par programmation à ces dossiers afin de les renommer avec un nom correctement formaté à l'aide d'un script bash? Et je suppose que par curiosité, comment diable est-ce arrivé en premier lieu?

Phill
la source
4
"Lorsque j'essaie de taper la saisie semi-automatique: ... Si j'essaye de taper ..." Pourquoi taper et ne pas laisser la saisie semi-automatique se terminer si pour vous? Aussi ^An'est pas littéralement ^suivi de A, mais Ctrl-A (vous pouvez le taper en utilisant Ctrl-V Ctrl-A car Ctrl-A est généralement un raccourci pour le shell).
muru
@muru qui ne fonctionne pas ... Je vais aussi loin que run_je dois taper quelque chose
Phill
Désolé commenté avant de voir votre montage, qui parvient à me faire entrer via cd
Phill
Duplication possible de Select unicode filename in Bash
muru
9
BTW, la "raison" pour laquelle mkdir dans matlab a fait cela est parce que les SEULS caractères invalides dans un nom de fichier ou de répertoire sur les systèmes de fichiers unix sont NUL et barre oblique /. Tout autre caractère est valide, y compris les caractères de contrôle. Je ne sais pas ce que matlab aurait fait si sp.run_number était égal à 0 (probablement abandonner avec une erreur ou produire run_, car l'octet NUL mettrait fin à la chaîne de nom du répertoire). Bien sûr, cela serait également problématique pour les valeurs 16 bits (ou plus) qui contenaient un octet NUL, et varierait également en fonction de l'endian-ness du système exécutant matlab.
cas

Réponses:

26

Vous pouvez utiliser l' renameutilitaire perl (aka prenameou file-rename) pour renommer les répertoires.

REMARQUE: cela ne doit pas être confondu avec renamefrom util-linuxou toute autre version.

rename -n 's/([[:cntrl:]])/ord($1)/eg' run_*/

Cela utilise la ord()fonction de perl pour remplacer chaque caractère de contrôle dans le nom de fichier par le numéro ordinal de ce caractère. par exemple ^Adevient 1, ^Bdevient 2, etc.

L' -noption est pour un essai à sec de montrer ce rename qui ferait si vous le laissiez. Supprimez-le (ou remplacez-le par -vpour une sortie détaillée) pour renommer réellement.

Le emodificateur de l' s/LHS/RHS/egopération fait que perl exécute le RHS (le remplacement) en tant que code perl, et $1est la donnée correspondante (le caractère de contrôle) du LHS.

Si vous voulez des nombres remplis par zéro dans les noms de fichiers, vous pouvez combiner ord()avec sprintf(). par exemple

$ rename -n 's/([[:cntrl:]])/sprintf("%02i",ord($1))/eg' run_*/ | sed -n l
rename(run_\001, run_01)$
rename(run_\002, run_02)$
rename(run_\003, run_03)$
rename(run_\004, run_04)$
rename(run_\005, run_05)$
rename(run_\006, run_06)$
rename(run_\a, run_07)$
rename(run_\b, run_08)$
rename(run_\t, run_09)$

Les exemples ci-dessus fonctionnent si et seulement si sp.run_number dans votre script matlab était dans la plage de 0..26 (donc il a produit des caractères de contrôle dans les noms de répertoire).

Pour gérer N'IMPORTE QUEL caractère de 1 octet (c'est-à-dire de 0 à 255), vous utiliseriez:

rename -n 's/run_(.)/sprintf("run_%03i",ord($1))/e' run_*/

Si cela sp.run_numberpouvait être> 255, vous devriez utiliser la unpack()fonction de perl au lieu de ord(). Je ne sais pas exactement comment matlab génère un int non converti dans une chaîne, vous devrez donc expérimenter. Voir perldoc -f unpackpour plus de détails.

Par exemple, ce qui suit décompressera les valeurs non signées 8 bits et 16 bits et les mettra à zéro sur 5 chiffres:

 rename -n 's/run_(.*)/sprintf("run_%05i",unpack("SC",$1))/e' run_*/
cas
la source
Merci pour les détails! J'essaie de le tester avec l' -noption, mais cela me dit que c'est une option non valide - les informations de version me donnent rename from util-linux 2.23.2donc je ne suis pas sûr que ce soit la même fonction
Phill
3
c'est pourquoi j'ai spécifié la version perl de l' renameutilitaire. util-linux« s renameest très différent, beaucoup moins capables, et les options de ligne de commande sont incompatibles. si vous utilisez debian ou similaire, essayez d'installer le file-renamepaquet. sinon installez le package approprié pour votre distribution. il peut déjà être installé, essayez d'exécuter prenameou file-renameau lieu de simplement rename.
cas
Oui, je pensais que c'était le cas. Je vais voir si je peux en faire fonctionner un. Merci encore d'avoir pris le temps de m'aider!
Phill
11

Et je suppose que, par curiosité, comment cela a-t-il pu arriver en premier lieu?

folder = [sp.saveLocation, 'run_', sp.run_number, '/'];

sp.run_numberétait un entier. J'ai oublié de le convertir en chaîne, mais pour une raison quelconque, je l'ai exécuté mkdir(folder); (dans matlab) a quand même réussi.

Ainsi, il semblerait que mkdir([...])dans Matlab concatène les membres du tableau pour créer le nom de fichier sous forme de chaîne. Mais vous lui avez donné un numéro à la place, et les chiffres sont ce que sont vraiment les personnages sur un ordinateur. Donc, quand sp.run_numberc'était 1, cela vous a donné le caractère avec valeur 1, puis le caractère avec valeur 2, etc.

Ce sont des caractères de contrôle, ils n'ont pas de symboles imprimables, et les imprimer sur un terminal aurait d'autres conséquences. Donc, au lieu de cela, ils sont souvent représentés par différentes sortes d'évasions: \001(octal), \x01(hex), ^Asont toutes des représentations courantes pour le personnage avec une valeur 1. Le caractère avec la valeur zéro est un peu différent, c'est l'octet NUL qui est utilisé pour marquer la fin d'une chaîne en C et dans les appels système Unix.

Si vous dépassiez 31, vous commenceriez à voir des caractères imprimables, 32 est un espace (pas très visible cependant), 33 = !, 34 = "etc.

Donc,

  • run_ run_^A/ run_^B/- Le premier run_correspond à celui avec un octet zéro, la chaîne se termine là. Les autres montrent que votre shell aime utiliser l'affichage des codes de contrôle avec ^A. La notation fait également allusion au fait que le caractère avec la valeur numérique 1 peut être entré comme Ctrl-A, bien que vous deviez dire au shell d'interpréter non pas comme un caractère de contrôle, mais comme un littéral, Ctrl-V Ctrl-Adevrait le faire au moins dans Bash.

  • ls: run_ run_? run_?- lsn'aime pas imprimer les caractères non imprimables sur le terminal, il les remplace par des points d'interrogation.

  • rsync: run_\#003/- celui-ci est nouveau pour moi, mais l'idée est la même, la barre oblique inverse marque une fuite, et le reste est la valeur numérique du caractère. Il me semble que le nombre ici est en octal, comme dans le plus courant \003.

  • en utilisant la commande ls | LC_ALL=C sed -n l... run_\006$ run_\a$ run_\b$ run_\t$- \a, \bet \tsont des échappements C pour alarme (cloche), retour arrière et tabulation, respectivement. Ils ont les valeurs numériques 7, 8 et 9, il devrait donc être clair pourquoi ils viennent après \006. L'utilisation de ces échappements C est encore une autre façon de marquer les caractères de contrôle. Les signes en dollars de fin marquent la fin de la ligne.

En ce qui concerne cd, en supposant que mes hypothèses sont correctes, cd run_devrait aller dans ce seul répertoire sans caractère de fin impair, et cd run_?devrait donner une erreur puisque le point d'interrogation est un caractère global qui correspond à n'importe quel caractère, et il y a plusieurs noms de fichiers correspondants, mais cdseulement attend un.

Laquelle de ces options est la représentation correcte du dossier?

Tous, dans un sens ...

Dans Bash, vous pouvez utiliser les échappements \000et entre guillemets pour représenter les caractères spéciaux, donc (octal) ou correspondre au répertoire avec la valeur de caractère 27 (qui se trouve être ESC). (Je ne pense pas que Bash supporte les échappements avec des nombres décimaux.)\x00$'...'$'run_\033$'run_\x1b'

la réponse de cas a un script pour les renommer, donc je ne vais pas y aller.

ilkkachu
la source
S'il s'agit de GNU ls, il existe des options de citation, y compris -b/ --escapeet --quoting-style=, ou la QUOTING_STYLEvariable d'environnement, pour contrôler la façon dont les caractères non imprimables sont affichés. Je ne pense pas qu'il y ait une option pour lui faire préférer les échappements octaux aux versions des personnages.
Toby Speight
3

Le plus simple serait de créer le mauvais nom de fichier et le bon nom de fichier dans le même environnement où l'incident s'est produit, puis de déplacer / renommer les dossiers avec les bons noms.

Pour éviter les collisions entre les noms existants, mieux utiliser un autre dossier de destination.

./saveLocationA/wrongname1 -> ./saveLocationB/correctname1
./saveLocationA/wrongname2 -> ./saveLocationB/correctname2
./saveLocationA/wrongname3 -> ./saveLocationB/correctname3

Si possible, je préférerais corriger le script et le relancer; réparer un bug post mortem bizarre coûte probablement plus cher et peut introduire de nouveaux problèmes.

Bonne chance!

Peter
la source