Вопрос или проблема
$ s=/the/path/foo.txt
мы можем извлечь по разным критериям отдельно
$ echo ${s##*/}
foo.txt
$ echo ${s%.txt}
/the/path/foo
Но если мы хотим извлечь по обоим критериям одновременно,
$ echo ${${s##*/}%.txt}
bash: ${${s##*/}%.txt}: неверная подстановка
Возможно ли достичь той же цели, используя только расширение параметров и не вводя временную переменную?
Может ли расширение параметров работать внутри другого расширения параметров каким-либо образом?
Спасибо.
Нет и да. В Bash или стандартной оболочке первая часть расширения должна быть параметром (т.е. переменной или позиционным параметром, или одним из специальных параметров), а не просто любым словом.
Bash:
Базовая форма расширения параметров — ${параметр}. Значение параметра подставляется. Параметр является параметром оболочки, как описано выше, или ссылкой на массив.
Текст в POSIX аналогично упоминает только параметры.
Вы можете использовать расширение в других частях расширений, так как они могут быть произвольными словами. Но это, конечно, не помогает в цепочках манипуляций с одной и той же строкой (как в вашем примере ${${s##*/}%.txt}
)
$ bash -c 's=/the/path/foo.txt; ext=.tyt; echo "${s%${ext/y/x}}"'
/the/path/foo
Zsh явно поддерживает цепочку, однако:
Если выражение параметра типа ${…} или командная подстановка типа $(…) используется вместо имени выше, оно сначала расширяется, и результат используется так, как если бы это было значение имени.
Если вы ищете способ получить компонент имени файла без расширения из пути (т.е. foo
из /the/path/foo.txt
) в одной операции, вы не можете использовать вложенное расширение параметра. Однако вы можете использовать сопоставление с регулярным выражением для его извлечения из пути:
s=/the/path/foo.txt
[[ "$s" =~ (.*/)?(.*)\. ]]
echo "Получено > ${BASH_REMATCH[2]} <" # "Получено > foo <"
Я не нашел способа удалить самое короткое неизвестное расширение из имени файла, такого как foo.bar.txt
, при этом обрабатывая компонент имени файла, у которого вообще нет расширения (например, foo
), если вы не готовы принять составное выражение:
[[ "$s" =~ (.*/)?(.*)(\.[^.]*)$ ]] || [[ "$s" =~ (.*/)?(.*) ]]
И если вы это сделаете, будет более эффективно выполнить две последовательные подстановки и просто уйти:
s=/path/to/foo
f=${s##*/}
echo "${f%.*}"
Ответ или решение
Внутри программирования на языке Bash, вопрос о вложении одной параметрической экспансии в другую является весьма актуальным, особенно когда требуется манипулировать строками и значениями переменных без создания дополнительных временных переменных. В этой статье мы рассмотрим возможности и ограничения вложения параметрических экспансий в Bash и сопутствующих языках script.
Теория
Параметрическая экспансия в Bash основывается на возможностях интерпретировать и заменять переменные в строках. Простое выражение ${parameter}
заменяет параметр на его значение. Это мощный инструмент для манипуляции строками, который поддерживает множество операций, таких как удаление префикса или суффикса, изменение значений на основании условий и т.д. Однако, из-за особенностей синтаксиса Bash, встроенное вложение экспансий внутри других экспансий не поддерживается. Bash требует, чтобы внутренняя часть параметрического выражения была параметром, а не произвольным выражением.
Пример:
s=/the/path/foo.txt
echo ${s##*/} # Выводит: foo.txt
echo ${s%.txt} # Выводит: /the/path/foo
Проблема возникает, если необходимо одновременно применить оба эти преобразования:
echo ${${s##*/}%.txt} # Ошибка: bad substitution
Пример
Возможность использовать вложенные экспансии отсутствует в стандартном Bash, но существует в других оболочках, таких как Zsh. Так, Zsh поддерживает вложение параметрических экспансий, что позволяет проводить более сложные операции над строками:
s=/the/path/foo.txt
echo ${${s##*/}%.txt} # Выводит: foo
Для стандартного Bash можно использовать комбинацию простых манипуляций или переходить к использованию регулярных выражений (RE) для извлечения необходимой информации. При ручной реализации такого функционала можно задействовать условные конструкции или команды для выполнения сложных преобразований.
Применение
Bash с использованием регулярных выражений
Один из методов обхода ограничения заключается в использовании регулярных выражений для разбиения строки и извлечения нужных компонентов:
s=/the/path/foo.txt
[[ "$s" =~ (.*/)?(.*)\. ]]
echo "Получено: ${BASH_REMATCH[2]}" # Выводит: Получено: foo
Этот подход соединяет мощь регулярных выражений с особенностями Bash, позволяя обходить ограничение на вложенные экспансии. Он гибок и подходит для более сложных случаев, таких как файлы без расширения или с несколькими точками в имени.
Двухступенчатое преобразование
Один из наиболее эффективных и допустимых в Bash решений — просто разбить процесс на две отдельные экспансии, что обеспечивает простоту и наглядность:
s=/path/to/foo.txt
f=${s##*/}
echo "${f%.*}" # Выводит: foo
Заключение
Вопрос о возможности вложения параметрических экспансий в Bash и других оболочках тесно связан с синтаксическими и функциональными ограничениями, заложенными в этих языках. Хотя стандартный Bash не предоставляет встроенной поддержки такого вложения, существует множество методик и стратегий, которые можно использовать для достижения той же цели. Понимание этих особенностей и умение применять различные обходные пути позволяют IT-специалистам эффективно работать с текстовыми данными и упрощать программные решения.