如何扩展波浪符号〜作为变量的一部分?


12

当我打开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


在ZSH中,这是x='~'; print -l ${x} ${~x}。在翻阅bash手册一段时间后,我放弃了。

@thrig:这不是bashism,此行为是POSIX。
WhiteWinterWolf

非常密切相关的(如果不是重复):unix.stackexchange.com/questions/151850/...
Kusalananda

@Kusalananda:我不确定这是一个骗子,因为这里的原因有些不同:OP在回显时没有将$ x括在引号之间。
WhiteWinterWolf

您无需在输入文件中输入波浪号。问题解决了。
chepner

Answers:


11

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标准的规定,引号删除发生在最后,参数扩展发生在很早。因此,${#"~"}在对外部报价进行评估之前,对进行了评估和扩展。依次,如参数扩展规则中所定义:

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

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

因此,换句话说,当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编程,我建议您阅读Advanced Bash Scripting Guide:它教Bash脚本,因此具有许多扩展和特色。与POSIX shell脚本相比,虽然有些吹毛求疵,但是我发现它写得很好,并带有大量实际示例。一旦解决了这个问题,就很容易在需要时将自己限制为POSIX功能,我个人认为对于初学者来说,直接进入POSIX领域是不必要的陡峭学习曲线(将我的POSIX波浪号替换为@ m0dular的类似于regex的Bash)等同于了解我的意思;)!)。


1:这使我发现Dash中的一个错误,该错误未在此处正确实现波浪号扩展(可使用验证x='~/foo'; echo "${x#~/}")。对于用户和Shell开发人员而言,参数扩展都是一个复杂的领域!


bash shell如何解析行x="${HOME}/${x#"~/"}"?它看起来像琴弦3的串联:"${HOME}/${x#"~/,和"}"。当内部一对双引号位于一个${ }块内时,外壳程序是否允许嵌套双引号?
安德鲁(Andrew)

@Andrew:我已经完成我的回答,并提供了其他信息,希望能解决您的评论。
WhiteWinterWolf

谢谢,这是一个很好的答案。我从阅读中学到了很多东西。希望我可以多次投票:)
安德鲁(Andrew)

@WhiteWinterWolf:无论结果如何,shell都看不到嵌套的引号。
AVP

6

一个可能的答案:

eval echo "$x"

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

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

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

给我:

/home/adrian/.config

请注意,该${parameter/pattern/string}扩展是Bash扩展,在其他Shell中可能不可用。
WhiteWinterWolf

真正。OP确实提到他正在使用Bash,所以我认为这是一个适当的答案。
m0dular

我同意,只要坚持使用Bash为何不充分利用它(并不是每个人都需要在任何地方都具有可移植性),但是对于非Bash用户来说值得一提(一些发行版现在随Dash一起提供,而不是Bash) ),因此受影响的用户不会感到惊讶。
WhiteWinterWolf

我冒昧地在题外话中提到了您的帖子,内容涉及Bash扩展和POSIX shell脚本之间的差异,因为我认为,与我的POSIX case结构相比,您的类似于Bash单行正则表达式的语句很好地说明了Bash脚本如何更加用户友好对于初学者。
WhiteWinterWolf
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.