如何通过“env”命令设置带有换行符的值?

如何通过“env”命令设置带有换行符的值?

我的意思是使用以下形式的GNUenv或 BSD命令:env

env [name=value ...] [utility [args ...]]

看起来没有办法value部分转义特殊字符,但我不太确定如何env实现解析该value部分。

我知道有很多方法可以通过 shell 的功能来做到这一点,但我想在没有 shell 帮助的情况下传递文字字符串(有点像execute envby exec)。也就是说,我需要找到某种带有换行符并受env命令支持的文字字符串格式。例如:

env FOO=LITERAL_STRING ruby -e 'puts ENV["FOO"]'

这里LITERAL_STRING应该包含一个带有换行符的文字字符串并且env应该理解该格式。

使用上述命令,预期输出应该是:

hello
world

我想知道是否可能。我将不胜感激你的帮助。

环境

  • env

    我使用 BSD env,所以它无法打印版本。不知道是否man可以帮助:

    $ man env | tail -n1
    BSD                             April 17, 2008                             BSD
    
  • 操作系统

    $ sw_vers
    ProductName:    macOS
    ProductVersion: 11.6
    BuildVersion:   20G165
    

答案1

你的理解是不正确的。当您将内容存储"hello\nworld"到变量中时,将按\n字面解释。仅当您调用诸如printfechowith -eflag 之类的工具时,它们才会在打印到控制台时扩展这些反斜杠序列。

在您的情况下,您希望将变量传递到环境并扩展换行符,建议使用ANSI C 风格的$'...'引用运算符ksh93 的版本,现在已得到其他几个 shell 的支持,包括 bash 和 zsh:

env FOO=$'hello\nworld' ruby -e 'puts ENV["FOO"]'

或者,如果可移植性是一个问题,一个简单的解决方案是将这些换行符放在您想要的位置

f="hello
world"

env FOO="$f" ruby -e 'puts ENV["FOO"]'

答案2

GNU 的FreeBSD 的 env有一个-S(“split”)选项[1],它将其参数拆分为空格,然后解释结果字符串中的大量转义符,包括但不限于\n

$ env -S 'foo=bar\nquux printenv foo'
bar
quux
$ env -S 'foo=bar\nquux' printenv foo
bar
quux

[1] 它的主要用途是用于#! ...shebang 行,但它也可以用在需要命令行的其他地方,但使用临时解析器而不是 shell 来解释它,就像-e大多数基于 vte 的选项一样终端。

答案3

env命令在执行时会从系统调用中获取许多参数(就像任何命令一样)execve()。执行它的进程将执行以下操作:

execve("/path/to/env", ["env", "-options", "var=value", "cmd", "arg"], environ);

并且env依次执行(通常在同一过程中):

execve("/path/to/cmd", ["cmd", "arg"], modified_environ);

其中modified_environisenvironvar=value附加到它,或者如果已经有一个或多个以var=in开头的字符串environ,则其中第一个(至少)替换为var=value

这些参数是以 NUL 结尾的字节数组,因此它们唯一不能包含的字节是 NUL 字节。这同样适用于名称和值都不能包含 NUL 字节的环境变量。

env启动时,从头到尾处理其参数。

开头的那些以 开头的-将被视为选项。

-单独也当作古形式来-i忽略environ

选项后面的那些--至少包含一个的选项(或可用于标记选项结尾的选项)=将被视为环境变量字符串(放在modified_environ上面)。第一个不包含字符的非选项参数=被视为命令名称(它将作为第一个参数传递给命令,也用于查找 中可执行文件的路径$PATH)。

此后的任何参数都将作为参数传递给命令,即使它们以字符开头-或包含=字符。

因此对于env其本身而言,只有-=字符是特殊的;-仅在选项处理期间,并且=仅在找到命令名称之前。

为了能够传递以 开头的环境变量名称-或以 开头的命令名称-,您可以使用--

execve("/usr/bin/env", ["env", "--", "-var-=value", "cmd"], environ);
execve("/usr/bin/env", ["env", "--", "-cmd-"], environ);

对于几种env实现方式,-单独的以下--仍然不会被视为命令名称。因此,如果您想调用名为 的命令-,则需要事先传递至少一个环境变量:

execve("/usr/bin/env", ["env", "--", "dummy=", "-", "args"], environ);

(或使用/path/to/-or./-代替-并绕过该命令的路径$PATH查找-)。

env但是,不允许您运行名称包含=字符的命令,并且无法=传递modified_environ.=在这种情况下,没有办法逃脱这个角色。

但就您的情况而言,除了 NUL 字节之外,env FOO=LITERAL_STRING没有任何字符本身会成为问题。LITERAL_STRINGenv

现在,当用某种语言编写代码来执行该env命令时,该语言中可能存在无法按字面输入的字符。

例如,在 中perl,您可以这样做:

exec "env", "--", "-text-=hello\nworld\n", "printenv", "--", "-text-";

将换行符表示为\n双引号内,但您也可以这样做:

exec qw(env --), q(-text-=hello
world
), qw(printenv -- -text-);

传递包含换行符的字符串。

在类似 Bourne 的 shell 语言的语法中,换行符在单引号或双引号内时并不特殊(例如 的q(...)qq(...)引用运算符perl),因此您可以这样做:

exec env -- "-text-=hello
world
" printenv -- -text

在 C shell 中,或者在 rc shell 中,情况会有所不同,其中"..."不是引用运算符。

你的env FOO=LITERAL_STRING ruby -e 'puts ENV["FOO"]'代码看起来像 shell 语言中的代码。该语法对于大多数 shell 都是有效的,因为几乎所有 shell 都将空格理解为单词分隔符和'...'引用运算符。

当您exec在开始时省略 时,shell 会在子进程中运行该命令,然后等待该进程终止或挂起。

如果这env FOO=LITERAL_STRING ruby -e 'puts ENV["FOO"]'是一种语言,不允许按字面输入换行符(可能不是 shell),也不允许使用某种形式的编码(\n, %0a, 
...),但显然支持'...'像大多数 shell 一样的引用运算符,除了 BSDenv -S已经把戏了由 @UncleBilly 建议,您可以调用解释器或可以执行命令并允许以某种编码方式指定换行符的语言,例如perl, ruby, python, ksh93, zsh, bash...

既然你已经有了ruby

ruby -e 'exec "env", "FOO=a\n\n\nb", *ARGV' -- ruby -e 'puts ENV["FOO"]'

但是任何血统编程语言都ruby可以自行设置环境变量,因此您不需要env

ruby -e 'ENV["FOO"]="a\n\n\nb"; exec *ARGV' -- ruby -e 'puts ENV["FOO"]'

答案4

在值中使用文字换行符:

$ env FOO="hello                                  
world" ruby -e 'puts ENV["FOO"]'

输出:

hello
world

如果您想将一个字符串\n转换为实际的换行符,请使用诸如 之类的实用程序printf,就像在其他地方一样:

$ env FOO="$(printf "%b" 'hello\nworld')" ruby -e 'puts ENV["FOO"]'
hello
world

env在这方面并不特殊(事实上,env只看到真正的换行符,因为它在进程开始之前就已扩展)。

相关内容