为什么双引号字符串中变量扩展后的字节会覆盖前面的字节?

Mic*_*out 3 shell text-processing newlines

我正在编写一个 shell 脚本来下载某些软件的最新版本。解析输出后,curl我通过几个步骤找到确切的版本字符串(在撰写本文时为0.65.3):

(在下面的所有代码示例中,>是我的提示。来自 Bash 3.2 或 Zsh 的输出位于没有>前缀的行上。)

> url="https://github.com/gohugoio/hugo/releases/latest"
> latest=$(curl --silent --head "$url" | grep Location)
> tag=$(echo "$latest" | cut -d'/' -f8)
> version=$(echo "${tag//v}")
> echo "hugo_${version}_Linux-64bit.tar.gz"
_Linux-64bit.tar.gz 
Run Code Online (Sandbox Code Playgroud)

我期望的输出是hugo_0.65.3_Linux-64bit.tar.gz,但是在使用echo带引号的字符串调用的输出中,似乎后面的字节${version}已被用来覆盖带引号的字符串开头的字节。

在这里,我使用两个不同的带引号的字符串来阐明正在发生的事情:

> echo "hugo_${version}test"
test_0.65.3
> echo "hugo_${version}lorem ipsum dolor sit amet"
lorem ipsum dolor sit amet
Run Code Online (Sandbox Code Playgroud)

如果我这样做,我会得到同样的意外结果:

> version=$(echo "${tag:1}")
> echo "hugo_${version}_Linux-64bit.tar.gz"
_Linux-64bit.tar.gz 
Run Code Online (Sandbox Code Playgroud)

但是,如果我这样做,我会得到预期的结果:

> version=0.65.3
hugo_0.65.3_Linux-64bit.tar.gz
Run Code Online (Sandbox Code Playgroud)

最后一个结果是必需的,但当然它使我的脚本静态而不是动态,因此对我来说不是很有用。如何在不硬编码$version脚本中的值的情况下获得所需的结果?

ctr*_*lor 16

bycurl返回的行以回车换行结束。(MS-dos 行结束)。Unix 工具删除了换行符,但最后会留下一个回车符。

修复这一行以使用dos2unix(并将您的参数引用到echo,避免BashPitfalls #14 中描述的错误):

version="$(echo "${tag//v}" | dos2unix)"
Run Code Online (Sandbox Code Playgroud)

...或者,使用 shell 的内置语法同时执行两种更改:

version=${tag//[$'v\r']/}
Run Code Online (Sandbox Code Playgroud)

dos2unix 确实进行了一些其他更改(例如在最后一行文本后添加一个尾随换行符,UNIX 需要但 DOS 不需要),但对于像这样的单行字符串,它们都无关紧要。

  • Michiel:是的,HTTP 标头_需要以 ASCII 字符 CR LF 结尾,许多(但不是全部)编程语言和工具`\r \n` 都有表示。 (4认同)
  • 我在 `grep` 之前将 `curl` 的输出传送到 `tr -d '\r'`(因为缺少 `dos2unix`),现在我得到了预期的结果。感谢您的解释 (3认同)

Ste*_*itt 14

ctrl-alt-delor回答解释了为什么您会看到这种行为;但是为了解决您的潜在目标,我建议使用GitHub API而不是解释“最新”重定向:

version=$(curl https://api.github.com/repos/gohugoio/hugo/releases/latest | jq -r '.tag_name | ltrimstr("v")')
Run Code Online (Sandbox Code Playgroud)

这会向 API 询问有关最新 Hugo 版本的信息,并使用 提取标签名称jq,去除任何前导“v”。

理想情况下,您甚至可以从返回的 JSON 中提取资产名称和 URL。