当我打开 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标准强制单词扩展按以下顺序完成(强调是我的):
波形符扩展(参见波形符扩展),参数扩展(请参阅参数扩展)、命令替换(请参阅命令替换)和算术扩展(请参阅算术扩展)应从头到尾执行。请参阅令牌识别中的第 5 项。
除非 IFS 为空,否则应对步骤 1 生成的字段部分执行字段拆分(请参阅字段拆分)。
除非 set -f 有效,否则应执行路径名扩展(请参阅路径名扩展)。
报价删除(请参阅报价删除)应始终最后执行。
我们唯一感兴趣的一点是第一点:正如您所看到的,波形符扩展是在参数扩展之前处理的:
- shell 尝试对 进行波浪号扩展
echo $x
,但没有找到波浪号,因此继续进行。 - shell 尝试对 进行参数扩展
echo $x
,$x
找到并扩展,命令行变为echo ~/someDirectory
。 - 处理继续,波浪线扩展已经处理完毕,
~
字符保持原样。
通过在分配 时使用引号$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#"~/"}"
,他会看到:
${HOME}
并且${x#"~/"}
必须扩大。${HOME}
扩展到变量的内容$HOME
。${x#"~/"}
触发嵌套扩展:"~/"
被解析,但被引用时被视为文字1。您可以在此处使用单引号以获得相同的结果。${x#"~/"}
表达式本身现在已扩展,导致前缀~/
从 的值中删除$x
。- 上面的结果现在被连接起来: 的扩展
${HOME}
、文字/
、扩展${x#"~/"}
。 - 最终结果用双引号括起来,从功能上防止分词。我在这里说功能性的,因为这些双引号在技术上不是必需的(参见这里和那里例如),但作为个人风格,一旦作业超出范围,
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 的建议。