Стандарт POSIX налагает расширение слов в следующем порядке (подчеркните, мое):
Должны выполняться расширение тильды (см. Расширение тильды), расширение параметров (см. Расширение параметров), подстановка команд (см. Подстановка команд) и арифметическое расширение (см. Арифметическое расширение), начиная с конца и до конца. См. Пункт 5 в разделе «Распознавание токенов».
Разделение полей (см. Разделение полей) должно выполняться на участках полей, сгенерированных на шаге 1, если IFS не равен нулю.
Расширение имени пути (см. Расширение пути) должно выполняться, если не задан параметр -f.
Удаление цитаты (см. Удаление цитаты) всегда должно выполняться последним.
Единственный момент, который нас интересует, это первый: как вы видите, расширение тильды обрабатывается до расширения параметра:
- Оболочка пытается развернуть тильду
echo $x
, тильда не найдена, поэтому она продолжается.
- Оболочка пытается расширить параметр
echo $x
, $x
находит и раскрывает, и командная строка становится echo ~/someDirectory
.
- Обработка продолжается, расширение тильды уже обработано,
~
персонаж остается как есть.
Используя кавычки при назначении $x
, вы явно просили не расширять тильду и рассматривать ее как обычный символ. Часто упускают из виду, что в командах оболочки вам не нужно заключать в кавычки всю строку, так что вы можете сделать раскрытие прямо во время присваивания переменной:
user@host:~$ set -o xtrace
user@host:~$ x=~/'someDirectory'
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
И вы также можете сделать так, чтобы расширение происходило в echo
командной строке, если оно может произойти до раскрытия параметра:
user@host:~$ x='someDirectory'
+ x=someDirectory
user@host:~$ echo ~/$x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Если по какой-то причине вам действительно нужно повлиять на тильду для $x
переменной без раскрытия и иметь возможность развернуть ее по echo
команде, вы должны выполнить два раза, чтобы вызвать два раскрытия $x
переменной:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ echo "$( eval echo $x )"
++ eval echo '~/someDirectory'
+++ echo /home/user/someDirectory
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Однако следует помнить, что в зависимости от контекста, в котором вы используете такую структуру, это может иметь нежелательный побочный эффект. Как правило, предпочитайте избегать использования чего-либо, требующего, eval
когда у вас есть другой путь.
Если вы хотите конкретно решить проблему тильды в отличие от любого другого вида расширения, такая структура будет более безопасной и переносимой:
user@host:~$ x='~/someDirectory'
+ x='~/someDirectory'
user@host:~$ case "$x" in "~/"*)
> x="${HOME}/${x#"~/"}"
> esac
+ case "$x" in
+ x=/home/user/someDirectory
user@host:~$ echo $x
+ echo /home/user/someDirectory
/home/user/someDirectory
user@host:~$
Эта структура явно проверяет наличие ведущего ~
и заменяет его на домашний каталог пользователя, если он найден.
После вашего комментария это x="${HOME}/${x#"~/"}"
действительно может удивить кого-то, кто не используется в программировании оболочки, но на самом деле связан с тем же правилом POSIX, которое я цитировал выше.
В соответствии со стандартом POSIX удаление кавычек происходит последним, а расширение параметров происходит очень рано. Таким образом, ${#"~"}
оценивается и расширяется задолго до оценки внешних кавычек. По очереди, как определено в правилах расширения параметров :
В каждом случае, когда необходимо значение слова (на основе состояния параметра, как описано ниже), слово должно подвергаться расширению тильды, расширению параметров, подстановке команд и арифметическому расширению.
Таким образом, правая сторона #
оператора должна быть правильно указана или исключена, чтобы избежать расширения тильды.
Иными словами, когда интерпретатор оболочки смотрит x="${HOME}/${x#"~/"}"
, он видит:
${HOME}
и ${x#"~/"}
должен быть расширен.
${HOME}
расширяется до содержимого $HOME
переменной.
${x#"~/"}
запускает вложенное расширение: "~/"
анализируется, но, будучи заключенным в кавычки, обрабатывается как литерал 1 . Вы могли бы использовать одинарные кавычки здесь с тем же результатом.
${x#"~/"}
Само выражение теперь расширяется, в результате чего префикс ~/
удаляется из значения $x
.
- Результат вышеупомянутого теперь объединен: расширение
${HOME}
, буквальное /
, расширение ${x#"~/"}
.
- Конечный результат заключен в двойные кавычки, функционально предотвращая разбиение слов. Я говорю функционально здесь, потому что эти двойные кавычки не являются технически необходимыми (см. Здесь и там, например), но как личный стиль, как только назначения получают что-то за пределами,
a=$b
я обычно нахожу более ясным добавление двойных кавычек.
Кстати, если присмотреться к case
синтаксису поближе , вы увидите "~/"*
конструкцию, которая опирается на ту же концепцию, что x=~/'someDirectory'
я объяснил выше (здесь снова, двойные и простые кавычки могут использоваться взаимозаменяемо).
Не волнуйтесь, могут ли эти вещи казаться неясными с первого взгляда (возможно, даже со второго или более позднего взгляда!) По моему мнению, расширение параметров с помощью подоболочек является одной из самых сложных концепций, которые нужно понять при программировании на языке оболочки.
Я знаю, что некоторые люди могут решительно не согласиться с этим, но если вы хотите более подробно изучить программирование оболочки, я советую вам прочитать Руководство по расширенному написанию сценариев Bash : оно учит методам написания сценариев Bash, поэтому с множеством расширений и колокольчиков свистки по сравнению со сценариями оболочки POSIX, но я нашел это хорошо написанным с множеством практических примеров. Когда вам это удастся, легко ограничить себя функциями POSIX, когда вам это необходимо, я лично считаю, что ввод непосредственно в область POSIX - это ненужная крутая кривая обучения для начинающих (сравните мою замену тильды POSIX с регулярным выражением @ m0dular типа Bash эквивалентно, чтобы понять, что я имею в виду;)!).
1 : Что приводит меня к нахождению ошибки в Dash, которая неправильно реализует расширение тильды (проверяется с помощью x='~/foo'; echo "${x#~/}"
). Расширение параметров - сложное поле как для пользователя, так и для самих разработчиков оболочки!
x='~'; print -l ${x} ${~x}
. Я сдался после того, как немного покопался вbash
руководстве.