La chaîne Bash remplace plusieurs caractères par un

8

Je remplace, à partir d'un titre de flux, tous les caractères sauf les lettres et les chiffres par un tiret pour utiliser le résultat comme nom de fichier sûr pour tout système de fichiers:

$ t="Episodie 06: No hope of riding home (NEW) - Advanced grammar"
$ echo ${t//[^A-Za-z0-9]/-}
Episodie-06--No-hope-of-riding-home--NEW----Advanced-grammar

Cependant, je voudrais condenser tous les tirets répétitifs avec un seul comme Episodie-06-No-hope-of-riding-home-NEW-Advanced-grammar

J'ai trouvé que je peux y arriver en utilisant une substitution en deux passes:

$ t="Episodie 06: No hope of riding home (NEW) - Advanced grammar"
$ tmp=${t//[^A-Za-z0-9]/-}
$ echo ${tmp//--/-}
Episodie-06-No-hope-of-riding-home-NEW--Advanced-grammar

Je pensais pouvoir le faire en un seul passage comme:

$ echo ${t//[^A-Za-z0-9]+/-}

mais ça ne marche pas.

Un indice?

Remarque: je ne veux pas aller avec sedou d'autres outils

neurino
la source

Réponses:

8

Vous avez besoin de quelque chose de plus puissant que les caractères génériques de shell traditionnels. En bash, définissez l' extgloboption, qui vous donne accès aux expressions régulières dans les modèles glob via une syntaxe inhabituelle héritée de ksh.

shopt -s extglob
sanitized=${raw//+([^A-Za-z0-9])/-}
Gilles 'SO- arrête d'être méchant'
la source
Merci, il y avait un commentaire de la réponse sous jw013 avec cette solution. Quelques informations sur la compatibilité avec d'autres shells de cette syntaxe? Je ne m'inquiète pas beaucoup à ce sujet, juste pour en savoir plus sur les shoptcoquilles qui le supportent.
neurino
@neurino shoptest spécifique à bash. La syntaxe de modèle qu'il active est toujours disponible dans toutes les variantes de ksh. Dans zsh, cette syntaxe doit être activée avec setopt ksh_glob. POSIX n'a ​​pas une telle fonctionnalité, ses caractères génériques sont moins puissants que les regexps. Les coquilles autres que bash / ksh / zsh, qui dans la pratique signifie principalement cendre de nos jours, ont tendance à coller aux caractères génériques POSIX.
Gilles 'SO- arrête d'être méchant'
bien, à ce stade , je préfère une plus grande compatibilité et la flexibilité avec un peu plus de frais généraux: echo "$t" | sed -r 's/[^[:alnum:]]+/-/g; s/^-|-$//'. J'accepte votre réponse car elle fait exactement ce qui a été demandé.
neurino
@neurino Si vous voulez la portabilité vers d'autres shells, alors vous pouvez opter pour la réponse de glenn jackman . Soit dit en passant, notez que la ${var/PATTERN/REPLACEMENT}construction est également spécifique à ksh / bash / zsh.
Gilles 'SO- arrête d'être méchant'
Je préfère sedcar je connais mieux sa syntaxe et son comportement, je peux facilement ajouter une instruction pour supprimer les tirets de début / fin, je n'ai pas besoin de me soucier du caractère \n. Est-ce sedbeaucoup moins disponible que tr?
neurino
7

tr est un bon outil pour ce travail

new=$( printf "%s" "$t" | tr -cs 'a-zA-Z0-9' '-' )
new=${new#-}; new=${new%-}
glenn jackman
la source
Merci, +1, je ne me souviens jamais de tr... Cependant, j'essayais de le faire dans Bash, sinon j'irais avec sed:echo "$t" | sed -r 's/[^A-Za-z0-9]+/-/g'
neurino
Down a voté parce qu'il est en conflit avecNote: I don't want to go with sed or other tools
Paul Calabro
3

Si vous voulez rester avec pure bash, vous devrez vous contenter de la solution à deux passes. Les substitutions de chaînes basiques utilisent des globes , comme dans l'expansion des chemins d'accès, et non des expressions régulières. Les seuls caractères spéciaux sont globs *, ?et []dont les équivalents bruts dans les expressions régulières sont .*, .et []. Jetez un œil au wiki Wooledge et aux sections de la page de manuel sur et pour plus d'informations.bash(1)Parameter ExpansionPathname Expansion

Tout comme un commentaire, une expansion en deux passes en bash pur est toujours plus rapide que d'essayer de faire la même chose en invoquant un programme externe, donc je ne m'inquiéterais pas trop.

jw013
la source
Merci, je vais vérifier le lien. Mon inquiétude est que je dois faire ce travail plus d'une fois dans tout le script, donc ma seule préoccupation était d'avoir le même code répété encore et encore, compromettant la lisibilité. Quoi qu'il en soit, je trouve une solution polie que je vais publier. Vive
neurino
Vous pouvez mettre ce code dans une fonction pour éviter de répéter le code.
jw013
C'est ce que je fais mais, comme vous le savez, les fonctions bash ne peuvent pas retourner de chaînes ... ou, du moins, c'était ce que je pensais avant il y a 10 minutes :)
neurino
4
Voici quelques exemples de choses à faire et à ne pas faire - Bash Extended Globbing .. Pour l'exemple ci-dessus, ce serait:shopt -s extglob; t="${t//+([^A-Za-z0-9])/-}"
Peter.O
1
@fered: merci, très intéressant, je vais le vérifier. Votre URL de lien a un caractère supplémentaire et renvoie un 404, celui qui fonctionne est Bash Extended Globbing
neurino