Je déteste les espaces dans les noms de fichiers

61

C'est simple. Je ne peux pas rester debout lorsque des personnes utilisent des espaces pour nommer des fichiers. Cela détruit parfois les commandes de la console et rend la sortie de ls moche.

Le défi consiste à écrire un programme (uniquement des caractères ascii) qui

  1. renomme tous les fichiers (y compris les répertoires) du répertoire en cours en versions avec espaces supprimés ou remplacés par '_'
  2. en cas de collision, vous devez ajouter un identifiant unique (à vous de choisir)
  3. descend de manière récursive dans tous les sous-répertoires

Vous pouvez assumer des chemins d'accès de style UNIX. Qui aurait besoin de ce programme sur une machine Windows de toute façon?

C'est le code golf, le programme le plus court gagne (#ascii caractères). Comme je déteste tellement les espaces, chaque espace doit être compté deux fois.

Veuillez indiquer votre langue, votre score, votre programme et une brève description de son exécution.

Le programme doit compiler et exécuter avec un effort raisonnable sur ma machine Linux.

EDIT: Etan ayant demandé une structure de fichier pour les tests, voici le script que j’utilise actuellement pour créer une arborescence de fichiers appropriée:

#!/bin/bash
rm -r TestDir

touchfiles()
{
    touch my_file
    touch my__file
    touch "my file"
    touch "my  file"
    touch " my_file  "
}

mkdir TestDir
cd TestDir

touchfiles

for dir in "Test Sub" Test_Sub "Te stSub" Te_stSub
do
    mkdir "$dir"
    cd "$dir"
    touchfiles
    cd ..
done
M.Herzkamp
la source
22
Ceci mendiant pour une solution faite sans caractères ascii.
Dennis Jaheruddin
50
Maintenant, je veux apprendre les
espaces blancs
10
@BrunoJ faire cela dans les espaces blancs nécessiterait tout d'abord de développer un système d'accès aux fichiers dans WS. Je pense que ce serait plus difficile que le défi réel.
Nzall
7
Attendre que quelqu'un poste une solution C / C ++ pour que je puisse la voler, la compiler, la poster en hexa comme code machine x86 avec ZERO espaces! [ou peut-être base64]
Mark K Cowan
10
Je déteste les traits de soulignement dans les noms de fichiers. Utilisez des tirets.
Dr. Rebmu

Réponses:

10

Zsh + GNU coreutils - 48 octets (1 espace)

for x   (**/*(Dod))mv   -T  --b=t   $x  $x:h/${${x:t}// }

C'est bizarre que vous détestiez les espaces (ASCII), mais que vous utilisiez des tabulations et des nouvelles lignes, mais je suppose que cela prend toutes sortes de choses.

zmv résout un grand nombre de problèmes de changement de nom de fichier de manière concise (et de manière légèrement obscure). Cependant, il insiste sur le fait que les cibles sont uniques; Bien que vous puissiez facilement ajouter des suffixes uniques, ajouter un suffixe uniquement si cela est nécessaire nécessite pratiquement de refaire tout le travail. Donc, au lieu de cela, je boucle manuellement et je fais confiance à GNU mv pour ajouter un identifiant unique en cas de collision ( --backupoption, plus --no-target-directorysi la cible est un répertoire existant, sinon mv, la source serait déplacée à l'intérieur de ce répertoire).

(od)est un qualificatif glob pour trier la sortie avec des répertoires apparaissant après leur contenu (comme dans find -depth). Dinclut les fichiers de points dans le glob. :het :tsont des modificateurs d’histoire similaires à dirnameet basename.

mvse plaint qu'il est appelé à renommer les fichiers eux-mêmes, parce que le glob comprend des noms de fichiers sans espaces. C'est la vie.

Version non-golfée:

for x in **/*\ *(Dod); do
  mv --no-target-directory --backup=numbered $x ${x:h}/${${x:t}// /}
done
Gilles, arrête de faire le mal
la source
1
cela ne renomme pas du tout mes fichiers!
M.Herzkamp
@ M.Herzkamp Oh, oui, les zmvbombes avant mvont une chance de régler les collisions. Ok, je le fais manuellement. Il s'avère que la longueur est exactement la même si je saute des fichiers à points et enregistre même un caractère si je ne le fais pas.
Gilles, arrête de faire le mal '11
1
Maintenant ça marche. Btw: J'ai inclus la pénalité d'espace à un moment où j'avais vraiment de la rancune contre les espaces;) Ironiquement, je n'ai pas exclu les espaces lorsque j'ai posté le défi: P
M.Herzkamp
13

Bash 116 octets, 16 espaces

find . -depth -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// /_}"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

Je n'ai pas supprimé les erreurs pour gagner quelques octets supplémentaires. Cela n'aura aucune collision.

Si un GNU non posix findpeut être attendu, cela peut être raccourci davantage:

Bash 110 octets, 15 espaces

find -d -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// /_}"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

Supprimer des espaces au lieu de les remplacer utilise deux octets de moins:

Bash 108 octets, 15 espaces

find -d -exec bash -c 'B=${0##*/}
M="${0%/*}/${B// }"
while [ -e "$M" ]
do M=$M.
done
mv "$0" "$M"' {} \;

Remarque: si des tabulations peuvent être utilisées à la place d'espaces, un seul espace est nécessaire (celui de la règle de correspondance pour la substitution à la ligne 2).

Merci à Dennis d'avoir trouvé un bogue sur les guillemets doubles (et d'avoir fourni une solution)

pqnet
la source
11
EST-CE QUE L'ESPACE SUPPLÉMENTAIRE DERRIÈRE trouve-t-il pour se moquer de moi ??? ;-)
M.Herzkamp
@ M.Herzkamp Je pensais que c'était une erreur de copier-coller, mais c'est en fait là. Je suppose que j'ai gagné 2 points de plus. En outre, -depthdans GNU peut être remplacé par -d, bien qu'il se plaint qu'il est obsolète. Je ne connais pas les règles du golf, puis-je le faire?
pqnet
2
Tant que cela fonctionne, je le permets. Si la dépréciation devenait la suppression dans une version future cependant, je devrais peut-être revenir à cette réponse et la voter pour qu'elle ne soit pas correcte ;-)
M.Herzkamp
2
Cela ne fonctionnera pas correctement si l’un des noms de fichiers contient une citation double. Pour résoudre ce problème, vous pouvez utiliser à la bash -c 'B=${0##*/}...' {} \;place, ce qui est en fait plus court.
Dennis
3
Je suppose que je serai ce gars, qu'est-ce qui se passe avec la Nvariable? Ce n'est jamais défini ...
Steven Penny
9

Python 180 octets

from    os  import*
t,c,h='.',chdir,path
def g(p):
    c(p)
    for x   in  listdir(t):
        if h.isdir(x):g(x)
        n=x.replace(' ','')
        while h.exists(n):n+=t
        if' 'in x:rename(x,n)
    c(t*2)
g(t)

seulement 2 espaces si vous utilisez la tabulation pour l'indentation :-)

Emanuele Paolini
la source
Je suppose que la plupart des autres réponses pourraient améliorer leur score en utilisant des tabulations au lieu d'espaces.
Kasperd
Mais votre soumission utilise des espaces, n'est-ce pas? (+1 pour le code de travail)
M.Herzkamp
Je ne sais pas comment mettre une tabulation dans la réponse ...
Emanuele Paolini
2
remplacé par des languettes :-)
Emanuele Paolini
3
Comme c'est moche ... Eh bien, je suppose que je l'ai demandé :(
M.Herzkamp
5

Si l'ordre des suffixes de fichiers entrés en collision n'a pas besoin de donner la priorité au fichier pré-existant, les opérations suivantes fonctionnent pour moi:

bash / find / mv 84 octets, 16 espaces

find -depth -execdir bash -c '[ "${0//[^ ]}" ] && mv -{T,--b=t} "$0" "${0// }"' {} \;

bash / find / mv 82 octets, 14 espaces

find -depth -execdir bash -c '[ "${0//[^ ]}" ]&&mv -{T,-b=t} "$0" "${0// }"' {} \;

Cuddled &&pour économiser deux octets d'espace.

bash / find / mv 60 octets, 11 espaces

find -d -execdir bash -c 'mv -{T,-b=t} "$0" "${0// }"' {} \;

Supprime la protection contre les erreurs afin que mv obtienne des erreurs sur les fichiers ne disposant pas d'espaces.

Edit: Supprimé les citations de {}comme rappelé par Dennis. Aussi autorisé findà crier sur la portabilité et la dépréciation dans la version la plus courte où mvcrie déjà au sujet de déplacer un fichier sur lui-même.

Edit 2: Ajouté -Tà la mvcommande pour éviter les répertoires imbriqués au lieu de renommer comme indiqué par pqnet. Expansion d'accolade utilisée au coût d'un caractère sur seulement un espace.

Etan Reisner
la source
Vous pouvez utiliser -dau lieu de -depthet vous n'avez pas besoin des guillemets {}.
Dennis
@ Dennis Oui. J'ai vu la -dconversation sur la réponse de pqnet, mais je me suis dit qu'étant donné que je faisais taire les mvcris, j'éviterais les findcris. Bien que je devrais probablement le raccourcir pour celui qui crie. Et oui, je cite toujours {}pour une raison quelconque, même si je sais que vous n'êtes pas obligé de le faire dans ce cas. Force d'habitude je suppose.
Etan Reisner
1
Quand une collision survient sur des noms de répertoire, il en mettra un dans un autre (et non des espaces). Utilisez l' -Toption pour mvéviter ceci
pqnet
Cela fonctionne, et j'ai dit dans le défi que l'annexe est à vous. +1
M.Herzkamp
4

NodeJS - 209 octets, 3 espaces blancs

s=require('fs');function a(d){s.readdirSync(d).forEach(function(f){f=d+'/'+f;i=0;r=f;if(/ /.test(f)){r=f.replace(' ','');while(s.existsSync(r))r+=i++;s.renameSync(f,r)}s.statSync(r).isDirectory()&&a(r)})}a('.');
cPu1
la source
Je ne suis pas familier avec node.js. Comment pourrais-je l'exécuter?
M.Herzkamp
Vous aurez besoin du nœud exécutable nodejs ; enregistrez-le dans un fichier et lancez-lenode file.js
cPu1
7
Je reçois une exception TypeError: Object #<Object> has no method 'exists'. Devinez où: c'est dans la ligne 1! : D
M.Herzkamp
Je l'ai testé. Quoi qu'il en soit, j'ai remplacé existe avec sa contrepartie synchrone. Pouvez-vous essayer maintenant?
cPu1
1
Je n'ai que la version 0.6.12 installée. Cela pourrait être le problème.
M.Herzkamp
2

Bash - 86 octets

find    .   -d|while    IFS=""  read    f;do    t=${f##*/};mv   --b=t   -T  "$f"    "${f%/*}"/${t// /};done
Subbeh
la source
Oups, allez jeter un oeil
Subbeh
2
De plus, les espaces sont comptés deux fois ;-)
M.Herzkamp
que voulez-vous dire exactement avec les espaces sont comptés deux fois?
Subbeh
1
Vous pouvez économiser beaucoup de caractères en abrégeant --backupà--b
1
Oui, maintenant cela fonctionne aussi avec mon ensemble de test! +1
M.Herzkamp
2

Bash + Perl rename64

( renameest le script Perl sur Debian et ses dérivés, pas la commande util-linux.)

find . -depth -name "* *" -execdir rename 'y/ /_/' * \;
german_guy
la source
11
Que se passe-t-il si "mon fichier.txt" et "mon_fichier.txt" sont tous deux présents?
M.Herzkamp
1
Oh c'est vrai ..
J'y
1
*devrait être {}, en l’état actuel, uniquement les fichiers dont le nom apparaît dans le répertoire en cours. Cela n’ajoute pas de suffixe en cas de collision. Vous pourriez économiser un peu en omettant -name "* *"car renameignore les fichiers dont le nom n'est pas transformé.
Gilles 'SO- arrête d'être méchant'
2

POSIX sh+ GNU find+ GNU mv67 octets ASCII + un espace (littéral)

find    -d  -exec   sh  -cf 'IFS=\ ;IFS=_   set $0;mv   --b=t   "$0"    "$*"'   {}  \;

Je ne sais pas si cela convient, mais avec cette séquence d'espaces est élidé pour un seul _- j'aime bien quand même. En fait, toute séquence, à l' exception des espaces de début / fin, est automatiquement tronquée (ce qui est également, je pense, un comportement bénéfique) . Merci à Gilles de l'avoir signalé.

Cela utilise simplement le séparateur de champs interne pour séparer les champs.

C'est assez ... bavard ...

... oh mec. Je savais que la tabulation n'était pas chère, mais je pensais que c'était au moins intelligent. Maintenant je suis juste en retard à la fête ...

Mikeserv
la source
Cela fonctionne sur mon ensemble de test comme vous le souhaitiez, mais pas selon les exigences du défi. J'aime ça, cependant, parce que j'apprendrai probablement quelque chose de nouveau. Je suppose que je vais devoir lire sur ce IFStruc magique ...
M.Herzkamp
1
@ M.Herzkamp - ifs se comporte différemment selon qu'il est défini sur un espace ou non. La plupart des gens le détestent parce qu'ils ne comprennent pas ses deux qualités principales - le fait qu'il ne fonctionne que sur les extensions ( $expandpas exagérées ) et la chose ifsws que nous venons de mentionner. Regardez ici
mikeserv
Cela ne renomme pas les fichiers dans les répertoires dont les noms contiennent des espaces. Un correctif serait de remplacer -execpar -execdir. Un autre problème IFSque vous ne mentionnez pas est que les espaces de fin sont supprimés. Notez que, comme d'autres l'ont remarqué, vous devez également avoir l' -Toption mv, lorsque la cible d'un mvappel est un répertoire existant.
Gilles 'SO- arrête d'être méchant'
@Gilles - Ma préférence serait d'utiliser sh -c 'mkdir -p ../newtree/"$0"; ln "$0"/* ../newtree/$0 {} \;et d'autres globs sur une find -type dcommande pour créer un arbre en miroir de liens durs, puis d'opérer sur ceux-ci, mais je ne suis pas sûr d'écrire un code-golf pour une opération de déplacement. Un bon point à propos des espaces de début / fin, bien que je pense que c'est aussi un comportement que je préférerais.
mikeserv
@Gilles - mais à propos, ce n'est pas un hasard - c'est un comportement voulu et contrôlé par des normes . La section Fractionnement de champ est parmi les très rares dans la spécification du shell qui ne contient pas les mots non spécifiés ou définis par l'implémentation . Il n'y a pas de telles garanties avec zshla fonction intégrée de, zmv par exemple.
mikeserv
2

PHP, 147 145 octets, 2 1 espace s -> 146

function    s(){foreach(glob("*")as$n){is_dir($n)&&chdir($n)&s()|chdir("..");if($n<$r=strtr($n," ",_)){while(file_exists($r))$r.=_;rename($n,$r);}}}

fonction récursive. Courir avecs(".");

Boucle à travers les globrésultats pour le chemin donné:

  • si répertoire, recurse
  • remplacer les espaces par un trait de soulignement
  • si les chaînes diffèrent
    • pendant que le nouveau nom de fichier est pris, ajoutez un trait de soulignement
    • renommer le fichier / répertoire
Titus
la source
php va renommer les fichiers sur le serveur ... Maintenant, je me demande comment changer les noms de fichiers d'un client chaque fois qu'il visite votre site: D
M.Herzkamp
1

Ruby 121

require 'find'

Find.find('.') do |file|
  if file.chomp.match(/ /)
    File.rename(file, file.gsub(/ /, '_'))
  end
end
gam3
la source
6
Bienvenue chez Code Golf! L'idée ici dans ces défis de code-golf est d'utiliser le plus petit nombre de caractères. Cela signifie que vous pouvez certainement vous débarrasser des lignes vierges et des tabulations et créer des noms de variable à un seul caractère, mais les gens recherchent toutes sortes de méthodes créatives pour réduire le nombre de caractères.
Pas que Charles
J'ai une erreur, que le répertoire n'est pas vide:gam3.rb:5:in `rename': Directory not empty - ./Te stSub or ./Te_stSub (Errno::ENOTEMPTY) from gam3.rb:5 from /usr/lib/ruby/1.8/find.rb:39:in `find' from /usr/lib/ruby/1.8/find.rb:38:in `catch' from /usr/lib/ruby/1.8/find.rb:38:in `find' from gam3.rb:3
M.Herzkamp
1

Python, 187

165, plus 22 points de pénalité pour les espaces.

from os import*
u='_';j=path.join
for t,d,f in walk('.',0):
 for z in f+d:
  n=z.replace(' ',u)
  if n!=z:
   while path.exists(j(t,n)):n+=u
   rename(j(t,z),j(t,n))

166, en utilisant le truc d' Emanuele :

Un seul espace dans celui-ci!

from    os  import*
u='_';j=path.join
for t,d,f   in  walk('.',0):
    for z   in  f+d:
        n=z.replace(' ',u)
        if  n!=z:
            while   path.exists(j(t,n)):n+=u
            rename(j(t,z),j(t,n))
Henry Keiter
la source
Cela fonctionne pour moi. +1
M.Herzkamp
supprime les espaces au début des lignes et utilise des tabulations - ce ne sont pas des espaces, donc ne comptez qu'une seule fois
chill0r
@ chill0r C'est ce que la deuxième version est; tous les espaces sont remplacés par des tabulations sauf un (sauf que SO les affiche toujours sous forme d'espaces).
Henry Keiter
1

LiveScript - 166

(Remplacez les espaces par des tabulations.)

(a=->(s=require \fs)readdirSync(it)map (f)->f=it+'/'+f;r=f.replace /\s/g,i='';(while f!=r&&s.existsSync r=>r+=i++);s.statSync(f)isDirectory(s.renameSync f,r)&&a r) \.

Basé sur la version optimisée de nderscore de la réponse de cPu1 .

nyuszika7h
la source
Travaux! +1 Je vais supprimer mes commentaires plus tôt pour ranger ce message.
M.Herzkamp
0

Bash 4+ 111 octets

shopt -s dotglob globstar
for f in **
do
n=${f// /}
while [[ $f != $n && -e $n ]]
do n+=1
done
mv "$f" $n
done

la source
1
Même problème que plusieurs autres entrées: vous remplacez les espaces dans les répertoires parents et mv ne les trouve pas. De plus, vous devez changer le sens de la traversée, sinon vous renommez les répertoires et mv ne peut pas trouver les fichiers qu’il contient.
M.Herzkamp
0

Groovy, 139 caractères

def c
c={
f->
def g=new File(f.parent,f.name.replaceAll('\\s',''))
f.renameTo(g)
!g.directory ?: g.eachFile(c)
}
new File('.').eachFile(c)

selon @ edc65 comment

Groovy, gérer les collisions, 259 caractères

def c
c={
p,l,f->
def g=new File(p,f.name.replaceAll('\\s',''))
f==g?:
(g.exists()?f.renameTo(g.toString()+l.indexOf(f.name)):f.renameTo(g))
!g.directory?:g.eachFile(c.curry(g,g.list().toList()))
}
def r=new File('.')
r.eachFile(c.curry(r,r.list().toList()))
s'identifier
la source
1
Cela ne gère pas les collisions.
edc65
Assurez-vous que les fichiers sont renommés avant leurs répertoires parents et que les espaces des répertoires parents ne sont pas remplacés.
M.Herzkamp
Je suis sûr que c'est ok
se connecter
0

POSIX (testé sur zsh) + commandes Linux de base 151

export IFS='
'
for f in $(ls -R1);do export n=$(echo $f|tr ' ' '_');yes n|mv $f $n || yes n|mv $f `echo $n;echo $f|md5sum`
done
LinGeek
la source
@ M.Herzkamp Fixe.
LinGeek
Plusieurs choses: quelle est la fonction d’export IFS et le c dans ls -cR? Et de quelle version de mv avez-vous besoin pour l'option --reply? (J'ai 8.13, et il ne reconnaît pas l'option). Aussi, pour obtenir un meilleur score, vous devez abréger vos noms de variables.
M.Herzkamp
Le c remplace les espaces par des nouvelles lignes. L'IFS empêche les espaces d'être des séparateurs. Le --reply provient d'anciennes versions et est sur le point d'être corrigé.
LinGeek
1
Il vous manque un deuxième mv dans la ligne 5? Et je pense qu'un écho dans cette ligne est faux.
M.Herzkamp
1
$(ls -CR)est complètement faux. Cette -coption est inutile et -Rvous permet d’obtenir des fichiers sans leur répertoire, ce qui est inutile. Fondamentalement, votre architecture ne gérera pas les noms de fichiers contenant des nouvelles lignes. Vous avez besoin set -fsinon les noms de fichiers contenant des caractères génériques vont exploser. exportest inutile. Je vois vaguement ce que vous essayez de faire pour unifier des fichiers, mais la tuyauterie est fausse.
Gilles 'SO- arrête d'être méchant'