Подстановка строк в Bash - это хорошо: $ {var // pat / rep}
val='Foo$%!*@BAR###baZ'
echo ${val//[^a-zA-Z_-]/_}
Foo_____BAR___baZ
Небольшое объяснение: косая черта вводит поиск / замену, немного похожий на sed (где он просто разграничивает шаблоны). Но вы используете одну косую черту для одной замены:
val='Foo$%!*@BAR###baZ'
echo ${val/[^a-zA-Z_-]/_}
Foo_%!*@BAR###baZ
Две косые черты // значит заменить все. Нечасто, но в нем есть логика, несколько косых черт, означающие многократную замену (прошу прощения за мой плохой английский).
И обратите внимание, как $ отделяется от переменной, но таким образом трудно изменить константу литерала (что было бы неплохо для тестирования). Изменение $ 1 не так уж и просто, афаик.