Bash-String ersetzt mehrere Zeichen durch eines

8

Ich ersetze aus einem Feed-Titel alle Zeichen außer Buchstaben und Ziffern durch einen Bindestrich, um das Ergebnis als sicheren Dateinamen für jedes Dateisystem zu verwenden:

$ 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

Ich möchte jedoch alle sich wiederholenden Striche mit einem einzigen wie verdichten Episodie-06-No-hope-of-riding-home-NEW-Advanced-grammar

Ich habe festgestellt, dass ich es mit einer Zwei-Pass-Substitution erreichen kann:

$ 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

Ich dachte, ich könnte es in einem einzigen Durchgang tun wie:

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

aber es funktioniert nicht.

Irgendeine Ahnung?

Hinweis: Ich möchte nicht mit sedoder anderen Tools gehen

Neurino
quelle

Antworten:

8

Sie benötigen etwas Stärkeres als herkömmliche Shell-Platzhalter. Legen Sie in bash die extglobOption fest, mit der Sie über eine ungewöhnliche Syntax, die von ksh geerbt wurde, auf reguläre Ausdrücke in Glob-Mustern zugreifen können .

shopt -s extglob
sanitized=${raw//+([^A-Za-z0-9])/-}
Gilles 'SO - hör auf böse zu sein'
quelle
Vielen Dank, es gab einen Kommentar von unter jw013 Antwort mit dieser Lösung. Einige Informationen zur Kompatibilität mit anderen Shells dieser Syntax? Ich mache mir nicht so viele Sorgen, nur um mehr darüber zu wissen shoptund welche Muscheln es unterstützen.
Neurino
@neurino shoptist spezifisch für Bash. Die aktivierte Mustersyntax ist immer in allen ksh-Varianten verfügbar. In zsh muss diese Syntax mit aktiviert werden setopt ksh_glob. POSIX hat keine solche Funktion, seine Platzhalter sind weniger leistungsfähig als reguläre Ausdrücke. Andere Shells als bash / ksh / zsh, was in der Praxis heutzutage meistens Asche bedeutet, bleiben in der Regel bei POSIX-Platzhaltern.
Gilles 'SO - hör auf böse zu sein'
Nun, an dieser Stelle bevorzuge ich mehr Kompatibilität und Flexibilität mit etwas mehr Overhead : echo "$t" | sed -r 's/[^[:alnum:]]+/-/g; s/^-|-$//'. Ich akzeptiere Ihre Antwort, da sie genau das tut, was in Frage gestellt wurde.
Neurino
@neurino Wenn Sie auf andere Shells portierbar sein möchten, können Sie die Antwort von Glenn Jackman verwenden . Beachten Sie übrigens, dass das ${var/PATTERN/REPLACEMENT}Konstrukt auch spezifisch für ksh / bash / zsh ist.
Gilles 'SO - hör auf böse zu sein'
Ich bevorzuge, sedda ich die Syntax und das Verhalten besser kenne. Ich kann leicht eine Anweisung hinzufügen, um Start- / Nachstriche zu entfernen. Ich muss mich nicht um \nchar kümmern . Ist sedviel weniger verfügbar als tr?
Neurino
6

tr ist ein gutes Werkzeug für diesen Job

new=$( printf "%s" "$t" | tr -cs 'a-zA-Z0-9' '-' )
new=${new#-}; new=${new%-}
Glenn Jackman
quelle
Vielen Dank, +1, ich erinnere mich nie an tr... Allerdings habe ich versucht, es in Bash zu erledigen, sonst würde ich gehen mit sed:echo "$t" | sed -r 's/[^A-Za-z0-9]+/-/g'
Neurino
Down stimmte ab, weil es im Widerspruch zuNote: I don't want to go with sed or other tools
Paul Calabro
3

Wenn Sie bei Pure Bash bleiben möchten, müssen Sie sich mit der Zwei-Pass-Lösung zufrieden geben. Bash-String-Ersetzungen verwenden Globs wie bei der Pfadnamenerweiterung und keine regulären Ausdrücke. Die einzigen Sonderzeichen in Kleckse sind *, ?und [], deren grobe Äquivalente in reguläre Ausdrücke sind .*, .und []. Werfen Sie einen Blick auf die Wooledge Wiki und die bash(1)Manpage Abschnitte auf Parameter Expansionund Pathname Expansionfür weitere Informationen.

Nur als Kommentar ist eine Erweiterung in zwei Durchgängen in Pure Bash wahrscheinlich immer noch schneller als der Versuch, dasselbe durch Aufrufen eines externen Programms zu tun, sodass ich mir darüber keine allzu großen Sorgen machen würde.

jw013
quelle
Danke, ich werde den Link überprüfen. Ich mache mir Sorgen, dass ich diese Arbeit mehr als einmal im gesamten Skript ausführen muss. Daher war meine einzige Sorge, dass derselbe Code immer wieder wiederholt wird, um die Lesbarkeit zu beeinträchtigen. Wie auch immer, ich habe eine höfliche Lösung, die ich veröffentlichen werde. Prost
Neurino
Sie können diesen Code in eine Funktion einfügen, um zu vermeiden , dass sich der Code wiederholt.
jw013
Es ist das, was ich tue, aber wie Sie wissen, können Bash-Funktionen keine Zeichenfolgen zurückgeben ... oder zumindest war es das, was ich vor 10 Minuten gedacht habe :)
Neurino
4
Hier sind einige Beispiele mit Do-s und-Don't-S - Bash Extended Globbing . Für das obige Beispiel wäre es:shopt -s extglob; t="${t//+([^A-Za-z0-9])/-}"
Peter.O
1
@fered: danke, sehr interessant, ich werde es überprüfen. Ihre Link-URL hat ein zusätzliches Zeichen und gibt eine 404 zurück. Die funktionierende ist Bash Extended Globbing
Neurino