如何在 ZSH 中正确导出包含空格的环境变量

如何在 ZSH 中正确导出包含空格的环境变量

添加包含空格的环境变量~/.zshrc

export SKIP="-Dskip1 -Dskip2"
export SKIP_NO_SPACES="-Dskip3"

尝试使用它

set -x  
mvn $SKIP $SKIP_NO_SPACES

正在执行的命令实际上是

+-zsh:108> mvn'-Dskip1-Dskip2'-Dskip3

只有包含空格的变量才会被引用。

如何避免添加单引号?

如果根本不使用引号/转义,则打开 shell 时会出现错误:

/.zshrc:export:11: 在此上下文中无效:-Dskip2

答案1

我认为您在这里(有点)滥用环境变量。该空间是变量的一部分,当您使用 执行参数扩展时$,它只会将该空间传递给命令。mvn获取一个参数 - 您的变量内容。

正如 Kamil Maciorowski 在评论中正确指出的那样,这是 Zsh 特有的。如果您省略引号,您所提议的内容将在 Bash 中起作用:

bash-5.0$ export test="foo bar"
bash-5.0$ ls $test
ls: bar: No such file or directory
ls: foo: No such file or directory
bash-5.0$ ls "$test"
ls: foo bar: No such file or directory

做你想做的事的一个替代方案是全局别名(也是 Zsh 的一个功能,而不是 Bash 的功能),可以在一行中的任意位置替换。它会保留空格:

$ alias -g SKIP="-Dskip1 -Dskip2"
$ alias -g SKIP2="-Dskip3"
$ mvn SKIP SKIP2

这只是将字符串“附加”到添加的位置。

答案2

问题与引用或转义无关,而与单词拆分(或缺少单词拆分)有关。在大多数 shell 中,如果您使用包含空格的变量而不在其周围加上双引号,则该值将根据该空格拆分为“单词”。因此,如果将变量SKIP设置为-Dskip1 -Dskip2(请注意,此值中没有引号或转义符),并且您像 一样使用它mvn $SKIP,则它等同于mvn "-Dskip1" "-Dskip2"

但 zsh 不是大多数 shell,并且(默认情况下)它不进行分词。如果变量设置为-Dskip1 -Dskip2,那么这就是传递给命令的内容,作为单一论点。您在输出中看到的引号set -x实际上并不是存在的,它们只是为了明确表示整个字符串作为单个参数传递。

有几种方法可以告诉 zsh 您想要完成分词。您可以=在扩展中使用修饰符:

mvn ${=SKIP}

或者你可以使用 shell 选项为所有扩展启用类似 sh 的拆分:

setopt shwordsplit
mvn $SKIP

...但是,这可能会带来令人不快的副作用,例如意外拆分您认为是单个参数的内容,以及将任何看起来像文件名通配符的内容扩展为文件名列表。 Shell 单词拆分往往会导致错误,这就是为什么它在 zsh 中默认被禁用的原因。在变量中存储多个字符串的真正正确方法是使用数组而不是普通变量:

SKIP=(-Dskip1 -Dskip2)
mvn "${SKIP[@]}"

这可以在 zsh、bash 和其他一些 shell 中工作(但不支持数组的 shell 除外)。但是,您无法从 zsh 导出数组(好吧,您可以运行export SKIP,但它什么也不做)。数组是 shell 的一个特性,而环境变量是操作系统的一个特性,它只支持纯字符串。您真的需要导出这些选项吗,还是您只是在 shell 中使用它们?

答案3

由于set -x,诊断行中出现了单引号。它们没有到达mvn。变量内容 ( )作为单个参数-Dskip1 -Dskip2到达 ,因为在 中您不需要严格引用变量(在符合 POSIX 标准的 shell 中需要)。mvnzsh

问题似乎出在zsh扩展变量的方式上。我认为您希望在不带引号时进行分词$SKIP就像在其他 shell 中发生的情况一样) 但zsh通常不会发生这种情况。

使用${=SKIP}替换后触发分词

例子:

% SKIP='-Dskip1 -Dskip2' 
% printf "%s\n" $SKIP   
-Dskip1 -Dskip2
% printf "%s\n" ${=SKIP}
-Dskip1
-Dskip2
% 

相关内容