Как я могу развернуть тильду ~ как часть переменной?

12

Когда я открываю приглашение bash и набираю:

$ set -o xtrace
$ x='~/someDirectory'
+ x='~/someDirectory'
$ echo $x
+ echo '~/someDirectory'
~/someDirectory

Я надеялся, что 5-я строка выше пошла бы + echo /home/myUsername/someDirectory. Есть ли способ сделать это? В моем исходном скрипте Bash переменная x фактически заполняется из данных из входного файла через цикл вроде этого:

while IFS= read line
do
    params=($line)
    echo ${params[0]}
done <"./someInputFile.txt"

Тем не менее, я получаю аналогичный результат, echo '~/someDirectory'вместо echo /home/myUsername/someDirectory.

Эндрю
источник
В ЗШ это есть x='~'; print -l ${x} ${~x}. Я сдался после того, как немного покопался в bashруководстве.
thrig
@thrig: Это не башизм, это поведение POSIX.
WhiteWinterWolf
Очень тесно связаны (если не дурак
Кусалананда
@Kusalananda: Я не уверен, что это обман, поскольку причина здесь несколько иная: ОП не заключал в кавычки $ x между кавычками.
WhiteWinterWolf
Вы не помещаете тильды во входной файл. Задача решена.
Чепнер

Ответы:

11

Стандарт POSIX налагает расширение слов в следующем порядке (подчеркните, мое):

  1. Должны выполняться расширение тильды (см. Расширение тильды), расширение параметров (см. Расширение параметров), подстановка команд (см. Подстановка команд) и арифметическое расширение (см. Арифметическое расширение), начиная с конца и до конца. См. Пункт 5 в разделе «Распознавание токенов».

  2. Разделение полей (см. Разделение полей) должно выполняться на участках полей, сгенерированных на шаге 1, если IFS не равен нулю.

  3. Расширение имени пути (см. Расширение пути) должно выполняться, если не задан параметр -f.

  4. Удаление цитаты (см. Удаление цитаты) всегда должно выполняться последним.

Единственный момент, который нас интересует, это первый: как вы видите, расширение тильды обрабатывается до расширения параметра:

  1. Оболочка пытается развернуть тильду echo $x, тильда не найдена, поэтому она продолжается.
  2. Оболочка пытается расширить параметр echo $x, $xнаходит и раскрывает, и командная строка становится echo ~/someDirectory.
  3. Обработка продолжается, расширение тильды уже обработано, ~персонаж остается как есть.

Используя кавычки при назначении $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#"~/"}", он видит:

  1. ${HOME}и ${x#"~/"}должен быть расширен.
  2. ${HOME}расширяется до содержимого $HOMEпеременной.
  3. ${x#"~/"}запускает вложенное расширение: "~/"анализируется, но, будучи заключенным в кавычки, обрабатывается как литерал 1 . Вы могли бы использовать одинарные кавычки здесь с тем же результатом.
  4. ${x#"~/"}Само выражение теперь расширяется, в результате чего префикс ~/удаляется из значения $x.
  5. Результат вышеупомянутого теперь объединен: расширение ${HOME}, буквальное /, расширение ${x#"~/"}.
  6. Конечный результат заключен в двойные кавычки, функционально предотвращая разбиение слов. Я говорю функционально здесь, потому что эти двойные кавычки не являются технически необходимыми (см. Здесь и там, например), но как личный стиль, как только назначения получают что-то за пределами, a=$bя обычно нахожу более ясным добавление двойных кавычек.

Кстати, если присмотреться к caseсинтаксису поближе , вы увидите "~/"*конструкцию, которая опирается на ту же концепцию, что x=~/'someDirectory'я объяснил выше (здесь снова, двойные и простые кавычки могут использоваться взаимозаменяемо).

Не волнуйтесь, могут ли эти вещи казаться неясными с первого взгляда (возможно, даже со второго или более позднего взгляда!) По моему мнению, расширение параметров с помощью подоболочек является одной из самых сложных концепций, которые нужно понять при программировании на языке оболочки.

Я знаю, что некоторые люди могут решительно не согласиться с этим, но если вы хотите более подробно изучить программирование оболочки, я советую вам прочитать Руководство по расширенному написанию сценариев Bash : оно учит методам написания сценариев Bash, поэтому с множеством расширений и колокольчиков свистки по сравнению со сценариями оболочки POSIX, но я нашел это хорошо написанным с множеством практических примеров. Когда вам это удастся, легко ограничить себя функциями POSIX, когда вам это необходимо, я лично считаю, что ввод непосредственно в область POSIX - это ненужная крутая кривая обучения для начинающих (сравните мою замену тильды POSIX с регулярным выражением @ m0dular типа Bash эквивалентно, чтобы понять, что я имею в виду;)!).


1 : Что приводит меня к нахождению ошибки в Dash, которая неправильно реализует расширение тильды (проверяется с помощью x='~/foo'; echo "${x#~/}"). Расширение параметров - сложное поле как для пользователя, так и для самих разработчиков оболочки!

WhiteWinterWolf
источник
Как оболочка bash разбирает строку x="${HOME}/${x#"~/"}"? Похоже конкатенации 3 строк: "${HOME}/${x#", ~/и "}". Разрешает ли оболочка вложенные двойные кавычки, когда внутренняя пара двойных кавычек находится внутри ${ }блока?
Андрей
@ Андрей: я дополнил свой ответ дополнительной информацией, надеюсь, с учетом вашего комментария.
WhiteWinterWolf
Спасибо, это отличный ответ. Я многому научился, прочитав это. Хотелось бы, чтобы я проголосовал больше, чем один раз :)
Эндрю
@WhiteWinterWolf: все равно оболочка не видит вложенные кавычки, какой бы ни был результат.
августа
6

Один из возможных ответов:

eval echo "$x"

Поскольку вы читаете ввод из файла, я бы не стал этого делать.

Вы можете найти и заменить ~ значением $ HOME, например так:

x='~/.config'
x="${x//\~/$HOME}"
echo "$x"

Дает мне:

/home/adrian/.config
m0dular
источник
Обратите внимание, что ${parameter/pattern/string}расширение является расширением Bash и может быть недоступно в других оболочках.
WhiteWinterWolf
Правда. ОП упоминал, что он использовал Bash, поэтому я думаю, что это правильный ответ.
17
Я согласен, пока кто-то придерживается Bash, почему бы не использовать его в полной мере (не всем нужна мобильность везде), но это просто стоит отметить для пользователей, не являющихся Bash (несколько дистрибутивов теперь поставляются с Dash вместо Bash, например ) так что пострадавшие пользователи не удивлены.
WhiteWinterWolf
Я позволил себе упомянуть ваш пост в своем экскурсии по поводу различий между расширениями Bash и сценариями оболочки POSIX, так как считаю, что ваше однострочное выражение, похожее на регулярное выражение Bash, по сравнению с моей caseструктурой POSIX хорошо иллюстрирует, как сценарии Bash особенно удобны для пользователя. для начинающих.
WhiteWinterWolf