我不太了解 shell 上的扩展和引用(zsh/bash)

Har*_*arv 3 bash zsh split for

我在这里阅读了一些关于在 shell 上引用变量的答案。基本上,这就是我正在运行的内容:

for f in "$(tmsu files)"; do echo "${f}"; done

这有预期的结果:文件列表。但是,我很确定它们是作为单个字符串出现的。经过一些阅读,我意识到这是一个 zsh-ism;它处理分裂的方式与大多数贝壳不同。

所以我启动bash并运行相同的命令。我原以为它会拆分列表,但唉,我得到了完全相同的结果。所以现在我真的很困惑。

  1. 为什么 bash 的行为不像预期的那样?
  2. 如何让 zsh 分割线?

我试过 (zsh): for f in "$(tmsu files)"; do echo "${=f}"; done 以及 for f in "$(=tmsu files)"; do echo "${f}"; done 都没有奏效,所以显然我误解了如何zsh拆分。

更糟糕的是,在某些时候,我完全按照自己的意愿去做了,但我不记得我是怎么做到的。

ilk*_*chu 6

for f in "$(tmsu files)"; do echo "${f}"; done
Run Code Online (Sandbox Code Playgroud)

这更像是错误的做法而不是 zsh-ism。双引号告诉 shell 将扩展结果保留为单个字符串。在大多数情况下,这就是您想要的,例如,如果您有

filename="foo bar"
ls "$filename"
Run Code Online (Sandbox Code Playgroud)

您希望ls将文件名作为单个参数,而不是作为两个参数foobar. (那些将是不同的文件名。)现在,您在那里使用命令替换的事实并没有改变这一点。尽管您在这种情况下引用并不能解决您的问题是正确的,但是这种结构一开始就很尴尬。(当然,在 POSIX shell 中,zsh 有自己的工具)。

考虑如下文件列表:

hello.txt
some silly name with spaces.txt
Run Code Online (Sandbox Code Playgroud)

使用"$(output filenames)"会给你一个字符串,而不是两个。
使用$(output filenames)会给你六个字符串,而不是两个。

您可以通过IFS仅设置换行符来解决该问题,因此后者会在换行符上拆分并为您提供两个文件名。但这很麻烦,具有全局影响,并且您仍然需要禁用通配符,set -f以防万一文件名被funny * name.txt替换。

(到目前为止,它仍然与 zsh 大致相同,因为它确实拆分了未加引号的命令替换的结果,即使它不拆分变量扩展。但默认情况下它不会对结果进行 globbing。)

现在,您看不到差异的原因是echo打印它获得的所有参数,并用单个空格连接。所以echo foo barecho "foo bar"产生完全相同的输出。相反,使用类似的东西printf "<%s>\n" ...,它可以让你更清楚地看到参数边界:

$ printf "<%s>\n" foo bar
<foo>
<bar>
$ printf "<%s>\n" "foo bar"
<foo bar>
Run Code Online (Sandbox Code Playgroud)

也可以看看: