如何更改“zsh”中变量的解释?

如何更改“zsh”中变量的解释?

我设置了一个变量TEMPP='-I ../dir1 -I ../dir2'

然后我跑

gcc -M $TEMPP somefile.c

包含文件的搜索列表中似乎没有包含../dir1and ../dir2,并且如果变量开头有空格,例如

TEMPP=' -I ../dir1 -I ../dir2'

它报告一个错误:

gcc: -I ../common1 -I ../encrypt: No such file or directory

所以看来该变量被视为一个文件。

也许它会让我分离目录以避免这个问题,但是包含的那些dirs是由另一个命令生成的,并且数量不是恒定的。

那么如何让命令中的变量被视为文字,看起来像是命令行中的手动输入,而不是整数字符串或文件名?

好吧...我发现这种情况只发生在 中zsh,但在 bash 中效果很好...可能是zsh变量的扩展比较特殊。

因此,如果有人能告诉我如何在 zsh 中很好地工作,我将不胜感激,否则我只能在 bash 中做到这一点。

答案1

"$TEMPP"我相信您正在尝试控制是否解释空白。 (注意,这不是 GCC 问题,而是您的 shell 正在解析命令行。)

这是bash做什么的:

$ F="foo bar baz"
$ for f in $F; do echo $f; done
foo
bar
baz

这是zsh做什么的:

$ F="foo bar baz"
$ for f in $F; do echo $f; done
foo bar baz

您需要zsh解释空白。在 zsh 中可能有一种优雅的方法来执行此操作(我不知道,但如果您重新标题并标记您的问题,您可能会得到更有用的答案)。这应该可以解决这个问题,但是对于这个问题来说有点沉重:

eval gcc -M $TEMPP somefile.c

答案2

在 zsh 中,与其他 Bourne 风格的 shell 不同,变量替换的结果不会拆分为被解释为通配符模式的单词。所以在 zsh 中,如果你写

a='hello *.txt'
echo $a

然后你就会看到hello *.txt,与其他 shell 不同,你会看到类似的东西hello bar.txt foo.txt

您可以为一个人打开分词功能扩张$=TEMPP。您可以使用setopt sh_word_split或为所有扩展打开分词emulate sh

您应该将这些命令放在 makefile 中,并让真正的 Bourne 风格的 shell(无论安装的是什么/bin/sh)运行它们。保留 zsh 供交互式使用和您自己的脚本。

答案3

保存可变数量值的变量称为数组。

var=value

是标量变量赋值语法,其中值被分配给a标量变量

var=( one two 'three or 3' )

数组变量赋值语法,您可以在其中赋值任何数字(甚至 0²)的值数组/列表变量

var[n]=foo

也是标量赋值语法,但赋值数组n 个元素的值。

所以,在这里你想要:

extra_options=( -I ../dir1 -I ../dir2 -I '/opt/my software/include' )
gcc -M $extra_options somefile.c

请注意,$extra_options扩展会跳过数组的空元素$extra_options(如果有),例如$scalar扩展为如果为空,则参数而不是一个空元素$scalar

您需要"$scalar"or"$array[@]""${(@)array}"(或"${array[@]}",与类似 ksh 的 shell 兼容,例如 bash 并让人想起"$@"Bourne shell 的 )来保留空元素。

请注意,在其他类似 Bourne 的 shell 中:

TEMPP='-I ../dir1 -I ../dir2'
gcc -M $TEMPP somefile.c

$TEMPP经历一个隐含的所谓分割+全局运算符,它并不(谢天谢地!)好像 的值$TEMPP已按原样嵌入到 shell 代码中,如gcc -M -I ../dir1 -I ../dir2 somefile.c.

如果是情况4,则:

TEMPP='foo>/etc/passwd;reboot' # or '$(reboot>/etc/passwd)'
echo $TEMPP

/etc/passwd例如,会覆盖并重新启动。 Shell 语法(包括;>运算符、${...}$(...)扩展、引号)在变量扩展时无法识别或处理。然而,它不像 zsh 或其他更理智的现代 shell(如 rc、es、fish、akanga...)或其他编程语言那样按原样扩展,而是经历 split+glob 运算符。

的值$TEMPP仅根据 的字符进行分割$IFS,然后进行通配(也称为文件名生成或路径名扩展)。这意味着:

TEMPP='-I ../dir1 -I ../dir2'
gcc -M $TEMPP somefile.c

仅当$IFS, 在解释该gcc...行时恰好包含空格字符且不包含 中的任何其他字符时才有效$TEMPP(幸运的是,默认值就是这种情况$IFS)。例如,如果$IFS包含igcc则将使用-I ../d,r1 -I ../dr2参数调用。

和:

TEMPP='-I ../dir1 -I ../dir2 -I "/opt/my software/include"'
gcc -M $TEMPP somefile.c

行不通,因为我们只是对 进行分割$IFS,而不是对 shell 语法(例如引用)进行解释,因此如果您必须传递包含空格的参数,则需要使用不同的分隔符来进行分割,例如:

TEMPP='-I,../dir1,-I,../dir2,-I,/opt/my software/include'
IFS=,
gcc -M $TEMPP somefile.c

例如。如果您的值包含*?、等通配符运算符[...],则需要set -o noglob在扩展之前发出命令以确保通配符不会扩展。也可以看看忘记在 bash/POSIX shell 中引用变量的安全隐患类似 Bourne 的 shell(zsh 除外)的错误设计所带来的安全影响。

在 zsh 中,如果你想要$IFS-splitting 和/或 globbing(但你通常不像大多数其他现代 shell 那样 zsh 有数组),你必须请求它明确地$=scalar在 上拆分$IFS$~scalar将 的内容$scalar视为模式并进行通配或$=~scalar两者兼而有之,因此相当于$scalar其他类似 Bourne 的 shell。

另请参阅${(s[whatever])scalar}以任意分隔符进行拆分,而不是使用$IFS,${(f)scalar}和作为在行eed(又名换行符)或 NUL${(0)scalar}上拆分的简写。f

如果您需要zsh解释为其他类似 Bourne 或类似 Korn 的 shell 编写的代码,您可以使用它shksh模拟,其中与这些 shell 的兼容性通过以下方式得到改进:

  • emulate ksh:明确更改模拟(尽管emulate zsh会恢复默认的理智模式。
  • emulate -L ksh:相同,但只是L局部的(例如在当前函数中)。
  • emulate ksh -c 'ksh/bash code here'或者emulate ksh -c 'source some-ksh-or-bash-script'仅解释该仿真模式下的给定代码。

在 sh 或 ksh 模拟中,IFS 分割和通配符是在列表上下文中的未加引号的参数扩展上隐式完成的,如 sh/ksh/bash(和shwordsplit以及globsubst影响 shell 行为的许多其他选项经过调整,以与那些其他外壳尽可能接近)。


1 受到set var = ( one two 'three or 3' )csh 的启发,csh 是第一个引入数组的 shell,后来被 ksh93(尽管早期版本的 ksh 也有set -A var one two 'three or 3'这种功能)、bash 或 yash 等其他 shell 复制。

² 注意:在ksh93中,var=()没有声明var为数组首先创建一个化合物变量而不是数组变量。

立方在 zsh 和所有其他 shell 中,但有一个例外:ksh(以及复制 ksh 而不是 zsh 的 bash),其中数组索引从 0 开始并且数组是稀疏的,因此array[1]=x如果还设置了索引为 0 的元素,则将分配第二个元素,array[123]=x如果所有这些都将分配第一个元素从 0 到 122 没有另外设置。

实际上,70 年代初的第一个(非常简单和原始的)Unix shell 就是这种情况,它没有变量,也没有命令替换,但有$1......$9像这样扩展的位置参数。

相关内容