如何扩展波形符 ~ 作为变量的一部分?

如何扩展波形符 ~ 作为变量的一部分?

当我打开 bash 提示符并输入:

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

我希望上面的第五行会消失+ 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

答案1

POSIX标准强制单词扩展按以下顺序完成(强调是我的):

  1. 波形符扩展(参见波形符扩展),参数扩展(请参阅参数扩展)、命令替换(请参阅命令替换)和算术扩展(请参阅算术扩展)应从头到尾执行。请参阅令牌识别中的第 5 项。

  2. 除非 IFS 为空,否则应对步骤 1 生成的字段部分执行字段拆分(请参阅字段拆分)。

  3. 除非 set -f 有效,否则应执行路径名扩展(请参阅路径名扩展)。

  4. 报价删除(请参阅报价删除)应始终最后执行。

我们唯一感兴趣的一点是第一点:正如您所看到的,波形符扩展是在参数扩展之前处理的:

  1. shell 尝试对 进行波浪号扩展echo $x,但没有找到波浪号,因此继续进行。
  2. shell 尝试对 进行参数扩展echo $x$x找到并扩展,命令行变为echo ~/someDirectory
  3. 处理继续,波浪线扩展已经处理完毕,~字符保持原样。

通过在分配 时使用引号$x,您明确请求不要展开波浪号并将其视为普通字符。经常被忽略的一件事是,在 shell 命令中,您不必引用整个字符串,因此您可以在变量赋值期间进行扩展:

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#"~/"}"对于未使用 shell 编程的人来说,这确实可能令人惊讶,但实际上与我上面引用的相同 POSIX 规则相关。

根据 POSIX 标准的规定,引号删除最后发生,参数扩展很早就发生。因此,${#"~"}远远早于外部报价的评估之前就对其进行了评估和扩展。依次,如定义参数扩展规则:

在每种需要 word 值的情况下(基于参数的状态,如下所述),word 应进行波形符扩展、参数扩展、命令替换和算术扩展。

#因此,必须正确引用或转义运算符的右侧,以避免波形符扩展。

因此,换句话来说,当 shell 解释器查看 时x="${HOME}/${x#"~/"}",他会看到:

  1. ${HOME}并且${x#"~/"}必须扩大。
  2. ${HOME}扩展到变量的内容$HOME
  3. ${x#"~/"}触发嵌套扩展:"~/"被解析,但被引用时被视为文字1。您可以在此处使用单引号以获得相同的结果。
  4. ${x#"~/"}表达式本身现在已扩展,导致前缀~/从 的值中删除$x
  5. 上面的结果现在被连接起来: 的扩展${HOME}、文字/、扩展${x#"~/"}
  6. 最终结果用双引号括起来,从功能上防止分词。我在这里说功能性的,因为这些双引号在技术上不是必需的(参见这里那里例如),但作为个人风格,一旦作业超出范围,a=$b我通常会发现添加双引号会更清晰。

顺便说一句,如果更仔细地观察语法case,您将看到其"~/"*构造依赖于我上面解释的相同概念x=~/'someDirectory'(这里再次强调,双引号和单引号可以互换使用)。

如果这些东西第一眼看起来很晦涩(甚至在第二次或以后看到时也可能如此!),请不要担心。在我看来,参数扩展和子 shell 是使用 shell 语言编程时需要掌握的最复杂的概念之一。

我知道有些人可能会强烈反对,但如果您想更深入地学习 shell 编程,我鼓励您阅读高级 Bash 脚本编写指南:它教授 Bash 脚本,因此与 POSIX shell 脚本相比,它有很多扩展和附加功能,但我发现它写得很好,有大量的实际示例。一旦你做到了这一点,当你需要时,很容易将自己限制在 POSIX 功能上,我个人认为,对于初学者来说,直接进入 POSIX 领域是不必要的陡峭学习曲线(将我的 POSIX 波形符替换与 @m0dular 的类似正则表达式的 Bash 进行比较)相当于了解我的意思;)!)。


1:这导致我在 Dash 中发现了一个错误,该错误没有正确实现波浪线扩展(可使用 进行验证x='~/foo'; echo "${x#~/}")。对于用户和 shell 开发人员来说,参数扩展都是一个复杂的领域!

答案2

一种可能的答案:

eval echo "$x"

由于您正在从文件中读取输入,因此我不会这样做。

您可以搜索并将 ~ 替换为 $HOME 的值,如下所示:

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

给我:

/home/adrian/.config

编辑:将搜索和替换更改${x/#\~/${HOME}}为仅替换初始的~.感谢@user137369 的建议。

相关内容