Supprimer les espaces, les tirets et les traits de soulignement dans les noms de fichiers?

10

Qu'est-ce qu'une bonne commande pour supprimer des espaces, des tirets et des traits de soulignement de tous les fichiers d'un répertoire ou des fichiers sélectionnés?

J'utilise la commande suivante avec Thunar Custom Actions pour slugifier les noms de fichiers:

for file in %N; do mv "$file" "$(echo "$file" | tr -s ' ' | tr ' A-Z' '-a-z' | tr -s '-' | tr -c '[:alnum:][:cntrl:].' '-')"; done

Mais cette commande ne remplace que les espaces par des tirets / tirets et des caractères plafonnés en minuscules.

J'ai utilisé la commande suivante dans le terminal pour supprimer des espaces de milliers de noms de fichiers dans un dossier, et cela a fonctionné assez rapidement:

 rename "s/ //g" *

Encore une fois, il supprime uniquement les espaces, et non les tirets / tirets et les soulignements également.

Idéalement, je ne veux pas d'espaces, de tirets / tirets et de soulignements dans mes noms de fichiers. Et ce serait formidable si la commande pouvait être utilisée avec des actions personnalisées Thunar sur les fichiers sélectionnés.

user8547
la source
2
Je note un problème que la plupart des solutions proposées ont, n'est pas de vérifier correctement l'existence du "nouveau" nom avant de déplacer le fichier. Ne pas le faire pourrait être la source potentielle de nombreux problèmes.
mdpc
Est-il possible de modifier la commande de John1024 pour vérifier cela?
user8547
@ user8547rename -i "s/[-_ ]//g" *
Sparhawk
Merci Sparhawk. Par ailleurs, pour ceux qui souhaitent l'utiliser comme une action personnalisée Thunar, la commande pour Thunar est: pour le fichier dans% N; faire mv "$ file" echo $file | sed -e 's/[ _-]//g'; fait
user8547

Réponses:

11

La version renamefournie avec le perlpackage prend en charge les expressions régulières:

rename "s/[-_ ]//g" *

Alternativement,

rename -i "s/[-_ ]//g" *

L' -iindicateur renameutilisera le mode interactif, invitant si la cible existe déjà, au lieu d'écraser silencieusement.

Le renommage de Perl est parfois appelé prename.

Renommer de Perl contre renommer d'Utilis-Linux

Sur les systèmes de type Debian, le changement de nom de perl semble être la valeur par défaut et les commandes ci-dessus devraient simplement fonctionner.

Sur certaines distributions, l' renameutilitaire d'util-linux est la valeur par défaut. Cet utilitaire est totalement incompatible avec Perl rename.

  • Tous: Tout d' abord, vérifiez si Perl renameest disponible sous le nom prename.

  • Debian: le renommage de Perl devrait être la valeur par défaut. Il est également disponible en prename. L' renameexécutable, cependant, est sous le contrôle de /etc/alternativeset aurait donc pu être modifié en quelque chose de différent.

  • archlinux: Exécuter pacman -S perl-renameet la commande est disponible en tant que perl-rename. Pour un nom plus pratique, créez un alias. (Pointe du chapeau: ChiseledAbs)

  • Mac OSX Selon cette réponse , renamepeut être installé sur OSX en utilisant homebrew via:

    brew install rename 
  • Téléchargement direct: rename est également disponible auprès de Perl Monks:

     wget 'http://www.perlmonks.org/?displaytype=displaycode;node_id=303814' -O rename
John1024
la source
Je pense que cela dépend de quoi renameparlez-vous. Celui de util-linux -2.24.2-1.fc20.x86_64 ne prend pas en charge les expressions régulières.
Cristian Ciupitu
1
@CristianCiupitu Je viens de vérifier la page de manuel pour la version de renommer que vous avez trouvée. D'après les arguments, la version renameutilisée par l'OP ressemble à la perlversion et non à la util-linuxversion.
John1024
Pour mémoire, il s'agit de la renamepage de manuel de la version util-linux . Quoi qu'il en soit, en plus de cette note, l'important est que le PO ait obtenu sa réponse (et vous avez une note positive de ma part :-D).
Cristian Ciupitu
@CristianCiupitu Merci d'avoir trouvé cela. De retour à vous avec un +1.
John1024
1
@ John1024 archlinux, mais j'ai découvert comment, allez-y pacman -S perl-renamealors je suppose que vous pouvez alias.
ChiseledAbs
5

Je remplacerais toutes ces trcommandes, par une sedcommande de substitution, par exemple:

for file in %N; do 
    mv "$file" "$(echo "$file" | sed 's/[ _-]//g')"
done
Cristian Ciupitu
la source
4

Sans compter mv, vous n'avez pas vraiment besoin d'un processus extérieur pour cela - vous pouvez en quelque sorte les faire caca .

ifsqz() ( LC_ALL=C sqz=$1
    isf() { [ -e "$1" ] || [ -L "$1" ] ; }  
    set -- * ; set -f
    for f do isf "$f" || break
    IFS=$sqz; set -- $f; IFS=
    isf "$*" || mv -- "$f" "$*"
    done
)

Cependant, cela signifie une mvinvocation par fichier, et c'est probablement renamemieux. Bien que cela devrait fonctionner avec seulement un POSIX mvdans $PATHet un shell POSIX.

Donc, j'ai trouvé une sorte de démo folle pour ça. L'ensemble de test est généré comme:

tee - - - - <<CGEN |\
dd cbs=90 conv=unblock |\
sed 'G;$!N'";s/^/touch -- '/;s/$/'/" |sh
$( #BEGIN CGEN
   LC_ALL=C
   i= n='"$((i=((i=i+1)==10||i==39||i==47)>0?(i+1):i))"'
   printf '%b -_   ---___'  $(
   IFS=0; eval \
       printf '"\\\\%04o\\\\%04o "' "$(
       printf "$n"' "$i" '%s $(
       printf %.252d
#END
))"))
CGEN

En premier lieu, je serai le premier à reconnaître que la commande ci-dessus produit des résultats qui peuvent être plus facilement obtenus par d'autres moyens. Mais d'autres moyens ne démontreraient probablement pas aussi bien ce qui pourrait être fait avec $IFSun peu d' imagination (malade?) .

Le premier morceau est donc assez simple:

  • tee transmet 5 copies de son entrée - le document hérité appelé CGEN

  • dd bloque son entrée par des sauts de ligne à 90 octets par bloc et des tuyaux qui ...

  • sedjoint 2 de ces blocs sur deux \ncaractères ewline, 'cite les résultats et ajoute la chaîne touch --à chaque cycle de ligne avant de passer à ...

  • sh qui exécute ensuite toutes les entrées sous forme de commandes shell

Le #CGENpeu que ... Eh bien, brièvement ...

  • le fond printfimprime 252 0s

  • le suivant du dernier reçoit 252 ''arguments de chaîne nulle et pour chacun imprime le contenu de $nsuivi de la chaîne" $i "

  • evalinterprète les arguments du suivant printfavant d'imprimer les résultats de cette interprétation sous forme de nombres octaux précédés de 2 contre-obliques par morceau

  • le dernier printfaffiche les valeurs d'octets pour ces octaux 2 à la fois, suivi de la chaîne -_ ---___pour chaque paire

  • $nest initialisé en une équation qui augmentera $id'une unité pour chaque évaluation, sauf qu'il ignore les valeurs 10, 39 ou 47 - (qui sont respectivement \newline, 'single-quote et /slash en ASCII décimal)

Le résultat final est un répertoire contenant beaucoup de noms de fichiers vraiment laids contenant tous les octets de mon jeu de caractères de 1 à 255, à l'exception du guillemet simple (ignoré uniquement pour éviter une autre sed s///instruction) et de la /barre oblique. Ces noms de fichiers ressemblent à ceci:

(set -- *; printf '%s\n\n##############\n\n%s\n' "${9}" "${34}")  | cat -A

   ---___ww -_   ---___xx -_   ---___yy -_   ---___zz -_   ---___{{ -_   ---___|| -_   ---$
$
___}} -_   ---___~~ -_   ---___^?^? -_   ---___M-^@M-^@ -_   ---___M-^AM-^A -_   ---___M-^BM-^B -_   ---___M-^CM-^C$
$
##############$
$
 -_   ---___M-ZM-Z -_   ---___M-[M-[ -_   ---___M-\M-\ -_   ---___M-]M-] -_   ---___M-^M-^ -_   ---___M-_M-_ -_$
$
---___M-`M-` -_   ---___M-aM-a -_   ---___M-bM-b -_   ---___M-cM-c -_   ---___M-dM-d -_   ---___M-eM-e -_   ---___$

Maintenant, je vais obtenir des données sur ces fichiers:

chksqz() ( LC_ALL=C sqz=$1
    set -- * ; set -f ; IFS= ; tc="$*"
    printf '#%s\n' \
        "There are $# files in this test directory." \
        "All filenames combined contain a total of ${#tc} bytes."
    IFS=$sqz ; set -- $* ; IFS= ; sc="$*"  
    printf "%s '$sqz'" \
        "#Of which ${#sc} bytes are not"\
        " and $((${#tc}-${#sc})) bytes are"
    set +f ; unset IFS
    printf ".\n#%s\n#Total:\t%d\n#Other:\t%d\n#'$sqz':\t%d\n" \
        "And to confirm these figures:" \
        $(  printf %s * | wc -c 
            printf %s * | tr -d "$sqz" | wc -c
            printf %s * | tr -dc "$sqz" | wc -c
))
chksqz '_ -'

PRODUCTION

#There are 101 files in this test directory.
#All filenames combined contain a total of 17744 bytes.
#Of which 2692 bytes are not '_ -' and 15052 bytes are '_ -'.
#And to confirm these figures:
#Total: 17744
#Other: 2692
#'_ -': 15052

D'accord. Maintenant enfin, à l'action:

ifsqz '_ -'
chksqz '_ -'

PRODUCTION

#There are 101 files in this test directory.
#All filenames combined contain a total of 2692 bytes.
#Of which 2692 bytes are not '_ -' and 0 bytes are '_ -'.
#And to confirm these figures:
#Total: 2692
#Other: 2692
#'_ -': 0

Succès! Vous pouvez voir par vous-même:

ls

????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????
??????????????????????
????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
??????????????????????????
???????????????????????????
???????????????????????????
???????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
????????????????????????????
??????????????????????????
????????????????????????
????????????????????
??????????????????
????????????????????????????
??
????????????????????????????
??????????????????????????
????????????????????????????
????????????????????????????
????????????????????!!""##
??????????????????!!""##$$
????????????????!!""##$$%%
????????????!!""##$$%%&&((
????????!!""??##$$%%&&(())
$$%%&&(())**??++,,..0011
%%&&(())**++??,,..00112233
&&(())**++,,??..0011223344
))**++,,..??0011223344556
**++,,..00??11223344556677
22334455667788??99::;;<<==>>
445566778899??::;;<<==>>??@@
5566778899::;;??<<==>>??@@AA
6778899::;;<<??==>>??@@AABB
8899::;;<<==??>>??@@AABBCCDD
\\]]^^``aa??bbccddeeffgghh
]]^^``aabbc??cddeeffgghhii
^^``aabbccdd??eeffgghhiijj
??@@AABBCCDDEE??FFGGHHIIJJKK
AABBCCDDEEFF??GGHHIIJJKKLLM
BBCCDDEEFFGG??HHIIJJKKLLMMNN
CCDDEEFFGGHHII??JJKKLLMMNNOO
EEFFGGHHIIJJ??KKLLMMNNOOPPQQ
ffgghhiijjkk??llmmnnooppqqrr
gghhiijjkkllmm??nnooppqqrrss
iijjkkllmmnn??ooppqqrrsstt
jjkkllmmnnoo??ppqqrrssttuuvv
kkllmmnnooppqq??rrssttuuvvww
LLMMNNOOPPQQRR??SSTTUUVVWWXX
MNNOOPPQQRRSS??TTUUVVWWXXYY
OOPPQQRRSSTT??UUVVWWXXYYZZ[[
PPQQRRSSTTUUVV??WWXXYYZZ[[\\
RRSSTTUUVVWW??XXYYZZ[[\\]]
ssttuuvvwwxx??yyzz{{||}}~~??
ttuuvvwwxxyyz??z{{||}}~~????
uuvvwwxxyyzz{{??||}}~~??????
wwxxyyzz{{||??}}~~??????????
xxyyzz{{||}}~~??????????????
YYZZ[[\\]]^^??``aabbccddee
ZZ[[\\]]^^``??aabbccddeeff
mikeserv
la source
2
+1 pour une utilisation créative de IFS+printf
John1024
@ John1024 - ce qui est vraiment amusant:set -- 'some arbitrary' args; eval printf '"%s\n"' "$(IFS=0; printf ' "$@" %s' $(printf %025d))"
mikeserv
1
new="$(IFS=" -_"; printf %s $1)"bifurque un sous-shell (sauf dans ksh93) et a des problèmes avec les sauts de ligne. Une autre option est d'utiliser IFS=' -_'; set -- $1; IFS=; new="$*"(et de changer votre boucle while en boucle for)
Stéphane Chazelas
1
[ -e x ]retournera false si xest un lien symbolique vers un fichier inexistant ou non accessible.
Stéphane Chazelas
1
Joli coquillage Kung-Fu!
contre-mode
2

si vous avez perl, vous devez généralement renommer. tu peux faire:

> type rename
rename is /usr/bin/rename

et montrez comment ce script est écrit:

> cat /usr/bin/rename | head -n 5 #firt 5 lines for example
#!/usr/bin/perl -w
#
#  This script was developed by Robin Barker ([email protected]),
#  from Larry Wall's original script eg/rename from the perl source.
#

Ce script ne prend pas en charge l'indicateur -i (c'est la version de mon système), mais peut-être que le vôtre le prend en charge. Et les arguments. Le premier est les expressions régulières au format PCRE, cela fonctionne comme un filtre, modifiez le nom d'entrée en nom de sortie. Liste des noms d'entrée que vous donnez par l'astérisque '*'. par exemple, vous faites:

> cd /tmp
> rename 's/ //g' *

en vrai '*' peut être étendu à:

> rename 's/ //g' file1 file2 file3 othe files found in current directory

Lorsque vous avez de très gros fichiers, vous êtes pris au piège. shell étendra votre ligne plus longtemps que le système n'accepte. alors vous pouvez contourner ce problème en utilisant find ou xargs. l'utilisation de 'find' est un problème, car le changement de nom sera appelé plusieurs fois égal au nombre de fichiers dans le répertoire. mieux utiliser xargs avec l'option -r. un appel de renommage modifie plusieurs fichiers. par exemple:

> ls | xargs -r rename 's/ //g'   #thats all, names will be appended at the end of this command.

dernier problème, qu'est-ce que cela signifie:

's/ //g'

il s'agit d'une expression régulière pour modifier les noms. après le premier «/» est l'espace. cela est détecté et remplacé par une chaîne après le deuxième «/». Mais il y a une chaîne vide terminée par un troisième '/', puis l'espace n'est remplacé par rien. L'option 'g' rend cette expression répétitive. expression marchera pour tous les noms du début à la fin et détectera tous les espaces.

Mais que faire si vous avez un caractère de tabulation ou un autre caractère «blanc»? il y a un remplacement pour ce '\ s'. quels autres personnages inutiles? ajoutez-le simplement à l'expression. Tous se ferment avec des crochets, par exemple:

's/[\s_-]//g'

C'est tout. voyez-vous la similitude? Je pense que vous devriez lire man perlrequick et man perlretut, cela vous explique (j'espère) comment fonctionne l'expression régulière. vous pouvez utiliser la commande rename dans votre propre script si vous en avez besoin.

Znik
la source
1

La shboucle de shell suivante supprimera tous les espaces, les traits de soulignement et les tirets des noms de fichiers dans le répertoire actuel, en prenant soin de ne pas écraser les fichiers existants:

for f in *; do
    test -f "$f" || continue
    nf=$( echo "$f" | tr -d ' _-' )
    ! test -e "$nf" && echo mv "$f" "$nf"
done

Pour bashet ksh, et étant un peu plus verbeux avec la logique:

for f in *; do
    if [[ -f "$f" ]]; then
        nf=$( tr -d ' _-' <<<"$f" )
        if [[ ! -e "$nf" ]]; then
            echo mv "$f" "$nf"
        fi
    fi
done

Retirez-le echolorsque vous êtes certain qu'il fait ce que vous voulez qu'il fasse.

La trcommande supprimera ( -d) tout caractère de l'ensemble de caractères donné ( ' _-'). Il est important d'avoir le tiret au tout début ou à la fin de l'ensemble, sinon il sera interprété comme une plage de caractères.

Kusalananda
la source