的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标准的规定,引号删除发生在最后,参数扩展发生在很早。因此,${#"~"}
在对外部报价进行评估之前,对进行了评估和扩展。依次,如参数扩展规则中所定义:
在每种情况下都需要单词的值(基于参数的状态,如下所述),单词应进行波浪号扩展,参数扩展,命令替换和算术扩展。
因此,#
必须正确引号或转义运算符的右侧,以避免波浪线扩展。
因此,换句话说,当shell解释器查看时x="${HOME}/${x#"~/"}"
,他看到:
${HOME}
并且${x#"~/"}
必须扩展。
${HOME}
扩展到$HOME
变量的内容。
${x#"~/"}
触发嵌套扩展:"~/"
被解析,但是被引用,被当作文字1。您可能在此处使用单引号产生相同的结果。
${x#"~/"}
现在,表达式本身已展开,导致~/
从的值中删除了前缀$x
。
- 上面的结果现在被连接起来:的扩展
${HOME}
,字面量/
,扩展${x#"~/"}
。
- 最终结果用双引号引起来,从功能上防止了单词拆分。我在这里功能说,因为这些双引号不是技术上需要(见这里和那里的实例),但作为个人风格,只要一个任务得到任何超出
a=$b
我通常发现有更清晰的加双引号。
顺便说一句,如果更仔细地看一下case
语法,您将看到该"~/"*
结构依赖于x=~/'someDirectory'
我上面解释的相同概念(此处,双引号和单引号可以互换使用)。
不用担心这些东西乍一看似乎是晦涩的(甚至在第二次或以后的情况下也是如此!)。我认为,使用子外壳程序进行参数扩展是使用外壳程序语言进行编程时要掌握的最复杂的概念之一。
我知道有些人可能会强烈反对,但是如果您想更深入地学习shell编程,我建议您阅读Advanced Bash Scripting Guide:它教Bash脚本,因此具有许多扩展和特色。与POSIX shell脚本相比,虽然有些吹毛求疵,但是我发现它写得很好,并带有大量实际示例。一旦解决了这个问题,就很容易在需要时将自己限制为POSIX功能,我个人认为对于初学者来说,直接进入POSIX领域是不必要的陡峭学习曲线(将我的POSIX波浪号替换为@ m0dular的类似于regex的Bash)等同于了解我的意思;)!)。
1:这使我发现Dash中的一个错误,该错误未在此处正确实现波浪号扩展(可使用验证x='~/foo'; echo "${x#~/}"
)。对于用户和Shell开发人员而言,参数扩展都是一个复杂的领域!
x='~'; print -l ${x} ${~x}
。在翻阅bash
手册一段时间后,我放弃了。