Строка Bash заменяет несколько символов одним

8

Я заменяю из заголовка канала все символы, кроме букв и цифр, тире, чтобы использовать результат в качестве безопасного имени файла для любой файловой системы:

$ 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

Однако я бы хотел сжать все повторяющиеся тире одним Episodie-06-No-hope-of-riding-home-NEW-Advanced-grammar

Я обнаружил, что могу добиться этого с помощью двухпроходной замены:

$ 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

Я думал, что смогу сделать это за один проход, например:

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

но это не работает

Любая подсказка?

Примечание: я не хочу использовать sedдругие инструменты

neurino
источник

Ответы:

8

Вам нужно что-то более мощное, чем традиционные символы оболочки. В bash установите extglobопцию, которая дает вам доступ к регулярным выражениям в шаблонах glob через необычный синтаксис, унаследованный от ksh.

shopt -s extglob
sanitized=${raw//+([^A-Za-z0-9])/-}
Жиль "ТАК - перестань быть злым"
источник
Спасибо, был комментарий от fered под ответом jw013 с этим решением. Некоторая информация о совместимости с другими оболочками этого синтаксиса? Я не беспокоюсь об этом, просто чтобы узнать больше о том, shoptкакие оболочки поддерживают это.
Нейрино
@neurino shoptспецифичен для bash. Синтаксис шаблона, который он включает, всегда доступен во всех вариантах ksh. В zsh этот синтаксис должен быть включен с setopt ksh_glob. POSIX не имеет такой функции, его шаблоны являются менее мощными, чем регулярные выражения. Оболочки, отличные от bash / ksh / zsh, которые на практике в настоящее время в основном означают пепел, обычно придерживаются подстановочных знаков POSIX.
Жиль "ТАК - перестань быть злым"
ну, в этот момент я предпочитаю более совместимость и гибкость с немного больше накладных расходов: echo "$t" | sed -r 's/[^[:alnum:]]+/-/g; s/^-|-$//'. Я принимаю ваш ответ, поскольку он точно делает то, что задал вопрос.
Нейрино
@neurino Если вы хотите переносимости на другие оболочки, то вы можете пойти с ответом Гленна Джекмана . Кстати, обратите внимание, что ${var/PATTERN/REPLACEMENT}конструкция также специфична для ksh / bash / zsh.
Жиль "ТАК - перестань быть злым"
Я предпочитаю, sedпоскольку я лучше знаю его синтаксис и поведение, я могу легко добавить оператор для удаления начальных / конечных тире, мне не нужно заботиться о \nсимволе. Является ли sedспособ менее доступным, чем tr?
Нейрино
7

tr хороший инструмент для этой работы

new=$( printf "%s" "$t" | tr -cs 'a-zA-Z0-9' '-' )
new=${new#-}; new=${new%-}
Гленн Джекман
источник
Спасибо, +1, я никогда не вспоминаю о tr... Однако я пытался сделать это в Bash, иначе я бы пошел с sed:echo "$t" | sed -r 's/[^A-Za-z0-9]+/-/g'
neurino
Голосовали против, потому что это противоречитNote: I don't want to go with sed or other tools
Пол Калабро
3

Если вы хотите остаться с чистым bash, вам придется согласиться на двухходовое решение. Подстановки строк Bash используют глобусы , как в раскрытии пути, а не регулярные выражения. Только специальные символы в комках являются *, ?и [], чьи грубыми эквивалентами в регулярных выражениях .*, .и []. Посмотрите вики Wooledge и разделы справочной страницы на и для получения дополнительной информации.bash(1)Parameter ExpansionPathname Expansion

Так же, как комментарий, двухпроходное расширение в чистом bash все еще, вероятно, будет быстрее, чем пытаться сделать то же самое, вызывая внешнюю программу, так что я бы не слишком беспокоился об этом.

jw013
источник
Спасибо, проверю ссылку. Меня беспокоит то, что я должен выполнять эту работу более одного раза по всему сценарию, поэтому моей единственной заботой было повторение одного и того же кода снова и снова, что ухудшит читабельность. В любом случае, я придумаю вежливое решение, которое собираюсь опубликовать. Приветствия
neurino
Вы можете поместить этот код в функцию, чтобы избежать повторения кода.
jw013
Это то, что я делаю, но, как вы знаете, функции bash не могут возвращать строки ... или, по крайней мере, это было то, что я думал раньше 10 минут назад :)
neurino
4
Вот несколько примеров с do-s и not-s- Bash Extended Globbing .. Для приведенного выше примера это будет:shopt -s extglob; t="${t//+([^A-Za-z0-9])/-}"
Peter.O
1
@fered: спасибо, очень интересно, проверю. URL вашей ссылки имеет дополнительный символ и возвращает 404, рабочий - Bash Extended Globbing
neurino