11

I'm replacing, from a feed title, all chars except letters and digits with a dash to use the result as a safe filename for any filesystem:

$ 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

However I'd like to condense all repeating dashes with a single one like Episodie-06-No-hope-of-riding-home-NEW-Advanced-grammar

I found I can achieve it using a two pass substitution:

$ 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

I thought I could do it in a single pass like:

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

but it doesn't work.

Any clue?

Note: I don't want to go with sed or other tools

3 Answers 3

11

You need something more powerful than traditional shell wildcards. In bash, set the extglob option, which gives you access to regular expressions in glob patterns through an unusual syntax inherited from ksh.

shopt -s extglob
sanitized=${raw//+([^A-Za-z0-9])/-}
6
  • Thanks, there was a comment from fered under jw013 answer with this solution. Some info on compatibility with other shells of this syntax? I'm not concerned about it that much, just to know more about shopt and which shells support it.
    – neurino
    Commented Nov 1, 2011 at 11:41
  • @neurino shopt is specific to bash. The pattern syntax that it enables is always available in all ksh variants. In zsh, this syntax must be enabled with setopt ksh_glob. POSIX has no such feature, its wildcards are less powerful than regexps. Shells other than bash/ksh/zsh, which in practice mostly means ash nowadays, tend to stick to POSIX wildcards. Commented Nov 1, 2011 at 13:53
  • well, at this point I prefer more compatibility and flexibility with a bit more overhead: echo "$t" | sed -r 's/[^[:alnum:]]+/-/g; s/^-|-$//'. I accept your answer as it exactly do what asked in question.
    – neurino
    Commented Nov 1, 2011 at 20:44
  • @neurino If you want portability to other shells, then you can go with glenn jackman's answer. By the way, note that the ${var/PATTERN/REPLACEMENT} construct is also specific to ksh/bash/zsh. Commented Nov 1, 2011 at 21:07
  • I prefer sed as I know better its syntax and behavior, I can easily add a statement to remove starting/trailing dashes, I don't need to care about \n char. Is sed way less available than tr?
    – neurino
    Commented Nov 1, 2011 at 21:12
9

tr is a good tool for this job

new=$( printf "%s" "$t" | tr -cs 'a-zA-Z0-9' '-' )
new=${new#-}; new=${new%-}
2
  • Thank you, +1, I never recall about tr... However I was trying to get it done in Bash, otherwise I'd go with sed: echo "$t" | sed -r 's/[^A-Za-z0-9]+/-/g'
    – neurino
    Commented Oct 31, 2011 at 18:07
  • Down voted because it conflicts with Note: I don't want to go with sed or other tools Commented May 24, 2018 at 21:41
4

If you want to stay with pure bash, you'll have to settle for the two-pass solution. Bash string substitutions use globs, as in pathname expansion, and not regular expressions. The only special characters in globs are *, ?, and [], whose rough equivalents in regular expressions are .*, ., and []. Take a look at the Wooledge wiki and the bash(1) man page sections on Parameter Expansion and Pathname Expansion for more info.

Just as a comment, a two-pass expansion in pure bash is still likely to be faster than trying to do the same thing by invoking an external program, so I wouldn't worry about it too much.

6
  • Thanks, I'll check the link. My worry is I have to do this work more than once in the whole script so my only concern was about having the same code repeated over and over compromising readability. Anyway I'm coming up with a polite solution I'm going to post. Cheers
    – neurino
    Commented Oct 30, 2011 at 23:34
  • You could put that code in a function to avoid repeating code.
    – jw013
    Commented Oct 30, 2011 at 23:38
  • It's what I'm doing but, as you know, bash functions cannot return strings... or, at least, it was what I thought before 10 minutes ago :)
    – neurino
    Commented Oct 30, 2011 at 23:48
  • 4
    Here are some examples with do-s and-don't-s -- Bash Extended Globbing .. For the above example, it would be: shopt -s extglob; t="${t//+([^A-Za-z0-9])/-}"
    – Peter.O
    Commented Oct 31, 2011 at 2:19
  • 1
    @fered: thank you, very interesting, I'll check it out. Your link url has an extra char and returns a 404, the working one is Bash Extended Globbing
    – neurino
    Commented Oct 31, 2011 at 9:40

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .