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

Вопрос или проблема

Когда я открываю оболочку 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.

Стандарт 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 с эквивалентом в Bash от @m0dular, чтобы понять, что я имею в виду 😉 !).


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

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

eval echo "$x"

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

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

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

Дает мне:

/home/adrian/.config

ИЗМЕНЕНИЕ: изменил поиск и замену на ${x/#\~/${HOME}}, чтобы заменить только начальный ~. Спасибо @user137369 за предложение.

Ответ или решение

Как расширить символ тильда ~ в Bash-переменной

Когда вы работаете с переменными в Bash, часто возникает необходимость обрабатывать пути к директориям, включая использование символа тильда (~), который обозначает домашнюю директорию пользователя. Однако, как показано в вашем примере, Bash не всегда расширяет "~" в переменных, если они заключены в кавычки. Давайте разберёмся, как добиться желаемого результата.

Проблема с расширением переменной

Вы задали переменную x следующим образом:

x='~/someDirectory'

При выполнении команды echo $x вы получаете ~/someDirectory, вместо ожидаемого /home/yourUsername/someDirectory. Это связано с тем, что расширение тильды происходит до расширения переменной, как указано в стандартных правилах POSIX.

Решения

1. Избегайте кавычек при присвоении

Если вы хотите, чтобы Bash автоматически расширил символ тильда при присвоении переменной, достаточно не заключать строку с тильдой в кавычки:

x=~/someDirectory
echo $x

В этом случае отобразится /home/ваше_имя/someDirectory.

2. Использование замены в строках

Если ваша переменная уже инициализирована с тильдой, можно удалить её и подставить путь до домашней директории:

x='~/someDirectory'
x=${x/#\~/$HOME}
echo $x

Это проще всего, если вы читаете данные из файла. В этом случае #\~ обозначает, что мы хотим заменить символ тильды в начале строки на значение переменной HOME.

3. Использование конструкции case

Вы также можете использовать конструкции case, чтобы более безопасно и наглядно обрабатывать замену:

x='~/someDirectory'
case "$x" in
  "~/"*)
    x="${HOME}/${x#~/}"
    ;;
esac
echo $x

Этот подход обеспечивает большую переносимость и читабельность, особенно если вы работаете с вводом от пользователя.

О предостережении использования eval

Хотя можно использовать eval для выполнения расширения переменных на основе строки с символом тильда, это может привести к проблемам с безопасностью и непредсказуемым поведением, особенно при обработке данных из файла:

eval echo "$x"

Такой подход не рекомендуется для использования с пользовательским вводом.

Заключение

Расширение символа тильда в Bash может казаться сложным из-за порядка выполнения операций над переменными. Ключевые моменты, которые следует помнить:

  • Избегайте кавычек при инициализации переменных с тильдой, если хотите, чтобы это расширялось автоматически.
  • Используйте замену строк и конструкции, такие как case, для безопасной работы с тильдой при обработке пользовательского ввода.
  • Будьте осторожны с использованием eval, чтобы избежать неожиданных последствий и проблем с безопасностью.

Эти подходы помогут вам успешно работать с путями в Bash и избежать распространенных ошибок при использовании символа тильда.

Оцените материал
Добавить комментарий

Капча загружается...