Fish-shell: 特殊情况 PATH/MANPATH/CDPATH 很奇怪; 我们需要一个更通用的解决方案,比如 zsh“绑定”变量

创建于 2012-12-11  ·  52评论  ·  资料来源: fish-shell/fish-shell

expand.h

/** Character for separating two array elements. We use 30, i.e. the ascii record separator since that seems logical. */
#define ARRAY_SEP 0x1e

/** String containing the character for separating two array elements */
#define ARRAY_SEP_STR L"\x1e"

这导致:

xiaq<strong i="10">@blackie</strong> ~> set a (printf 'a\x1eb')
xiaq<strong i="11">@blackie</strong> ~> count $a
2
xiaq<strong i="12">@blackie</strong> ~> set a (printf 'a\x1fb')
xiaq<strong i="13">@blackie</strong> ~> count $a
1

很明显,char \x1e 被特别视为元素分隔符。

enhancement

所有52条评论

是否担心无法代表 \x1e? 或者您是否正在考虑架构改进?

我认为数组分隔符主要用于在只接受字符串的地方持久化数组,比如通用变量或环境变量。

201 年 2 月 12 日上午 2 点 09 分,“ridiculousfish”通知@github.com 表示

是否担心无法代表 \x1e? 或者你在想
关于架构改进?

我会说我有两个方面的想法。 对于前者,只需考虑一个
嵌入 \x1e 的文件名。 POSIX 说除了 \0 之外的任何内容
文件名,所以这是完全可能的。 使用 \0 作为数组分隔符可能是
一个稍微好一点的选择,但这导致了第二个问题——它是
脆弱且明显错误。

我认为数组分隔符主要用于在地方持久化数组
只接受字符串,如通用变量或环境变量。

如果持久化意味着“序列化”,则不。 数组总是存储在
\x1e 分隔的形式。 导出的环境数组由“:”连接。 他们
虽然在通用变量中使用 - 恕我直言,应该实施
而是使用适当的转义。


直接回复此邮件或在 GitHub 上查看。

使用私人使用区域字符作为分隔符怎么样? 在某些情况下,Fish 已经使用了其中的一些。

@JanKanis这也好不到哪里去; 在文件名(考虑使用非 utf8、本机编码的文件系统)和其他字符串中完全有可能。

我想说的是, "\0"是一个 _slightly_ 更好的选择,因为这是 UNIX 禁止在文件名中使用的唯一字符,但它对我来说仍然有难闻的气味。 此外,我们已经对数组进行了很多拆分和组装,我希望实现真正的数组能够带来更好的架构和更少的代码。

当将外部字符串编码为 wchars 时,Fish 已经处理了私有字符和无效字节。 这些特殊值被逐字节编码成一组特定的专用字符,fish 也会在输出时再次解码,因此原则上使用另一个专用字符可以工作。 但是我同意使用真正的数组要好得多。 fish 和 fishd 之间的通信发生在使用 utf8 字符串的套接字上存在一个复杂性,并且 fish 使用(我认为)转义序列“\x1e”(而不是 0x1e 字节)来分隔数组项。 但这可能可以通过使用例如私有未使用的转义序列来解决。

我同意 xiaq 的担忧,但是(对于实际的)我发现 \n 上的隐式拆分更加令人反感:

a<strong i="6">@raspeball</strong> ~> count (printf 'a\x1eb')
1
a<strong i="7">@raspeball</strong> ~> count (printf 'a\nb')
2

子进程输出的(换行符分隔的)数组解释应该是明确的和可选的!

但这可能与数组的底层存储无关......

@xiaq :使用\0有什么问题? xargs似乎将其用作“可靠”选项。

您不能在环境变量中使用\0 ,因此很难将数组导出到子 shell。

我正在从 zsh 迁移,我使用此序列通过利用其“绑定”变量功能以理智的方式定义 $LESS env var:

typeset -xT LESS less ' '
less=(
    --HILITE-UNREAD
    --LONG-PROMPT
)

为简洁起见,我省略了完整的选项列表。 在 zsh 中,这导致 $LESS env var 是 $less 数组中选项的空格分隔列表。 fish 中的等效项导致元素由记录分隔符 (\x1e) 字符分隔。 尽管文档说数组的元素将由空格分隔(以特殊数组为模,例如 PATH)。 我必须明确地执行将值插入单个字符串的赋值以获得预期的结果:

set -x LESS --HILITE-UNREAD \
    --LONG-PROMPT
set LESS "$LESS"

目前我并不关心是否在内部使用 \x1e 而不是 \x00 来序列化数组。 我确实关心导出的数组的元素由 \x1e 分隔。 那只是坏了,错误的,fubar。 选择你的形容词。 它也与上述解决方法和记录的行为不一致。 此问题应标记为错误恕我直言。

PS,文档中没有提到使用记录分隔符(\x1e)。 这是另一个问题。

@krader1961感谢分享。 对于类似列表的环境变量,没有标准的 Unix 约定——有些是冒号分隔的,有些是空格分隔的。 fish 使用 \x1e 因此它可以区分自己的数组。

你能指出错误的文件吗?

您认为如何导出数组 - 冒号、空格、换行符等等? 鱼也应该在这个角色上标记环境变量吗?

看起来 less 期望以空格分隔的参数。 可能最简单的解决方法是set -x LESS '--HILITE-UNREAD --LONG-PROMPT'等。

像环境变量这样的列表没有标准,因为根据定义,它们是任意字节序列,由等号分隔的键和值组成,并以空字节终止。 它们甚至不必是可打印的字符。 唯一被广泛接受的更高抽象级别的约定是 execlp() 函数为 PATH env var 建立的约定。

该文档是错误的,因为它没有提到在导出具有多个元素的 var 时使用 \x1E、\036、30 或“记录分隔符”字符来分隔数组的元素。 该文件确实指出

..., and array variables will be concatenated using the space character.

这来自http://fishshell.com/docs/current/index.html 中的“变量扩展”部分。 可以合理地推断,该语句也适用于导出的 var,这些 var 不是在同一文档的“数组”和“特殊变量”部分中记录的特殊情况。

我的感觉是 fish 不应该自动将环境变量标记到冒号分隔的特殊情况变量(例如 PATH)之外的列表中。 但是,应该有一种强大的方法,用户可以通过该方法将 var 标记为任意字符的数组。

缺少一种机制来配置要在 var by var 基础上使用的字符(ala zsh "typeset -T" 命令),在连接数组元素时应使用空格(同样,不包括冒号分隔的特殊情况 vars )。 显然,这不适用于私有数据存储,例如通用变量的存储。

最后,我在标准 fish 函数中找不到任何用途,其中 env var 用于将包含多个元素的数组传递给另一个函数或脚本。 这样的用例可能存在,但它应该要求脚本在数据的序列化/反序列化中明确合作,而不是依赖 fish 从字符串包含记录分隔符的 var 中隐式重建数组。

感谢您周到的回复。 您引用的关于使用空格连接的部分专门用于双引号字符串。 我们应该添加一些关于导出数组会发生什么的讨论。

用户可以使用例如set SOMEPATH (string split : $SOMEPATH)标记字符串。

空间连接导出变量的缺点是当 fish 递归运行时它们会发生变化。 今天这有效:

> set -x list 1 2 3
> count $list
3
> fish -c 'count $list'
3

但是,如果我们使用空格导出,则递归调用将显示 1。 正如您所说,我们不依赖于此,但从一致性的角度来看,这很好。

感谢您周到的回复。

我必须支持它! 对事物有新的看法总是很高兴。

对于本次讨论的新手,我想我必须引入一些与此相关的内容。

立即想到的是listify whitelist ,它出现在 #2090 等问题中。

这意味着对于 $PATH、$CDPATH 和 $MANPATH,它们将以列表/数组的形式出现,但在导出时,它们将再次与“:”连接。 然后鱼里面的鱼会再次分裂它们。 这适用于冒号,而不是 \x1e。 根据我对代码的理解,它似乎在每个冒号上都执行此操作,没有转义的机会,因此它可能会在其中包含冒号的 $PATH 条目上中断 - UNIX 允许在文件路径内部,尽管它似乎至少对于 $PATH 已损坏. 该方案也用于例如 PYTHONPATH 和 GOPATH。

我希望有一些比隐式 always-split-on-\x1e-except-for-these-three-split-them-on-colon 更明确的拆分环境变量的方法,因为这实际上是两种不同的方案一个和当前导出一个列表总是会混淆除了鱼之外的所有东西。

我首选的解决方案是splitenv var1 var2 var3之类的函数:

function splitenv --no-scope-shadowing
    set -e IFS # string split doesn't have superpowers, so unset IFS lest we split on both : and \n
    for arg in $argv
        set -q $arg; and set $arg (string split ":" $$arg)
    end
end

(如果string split超能力,这会简单一点)

然后,所有列表在导出时都将用冒号连接,因此用户可以使用splitenv显式取消连接它们(尽管我并没有死心于辅助函数,但我相信让这个微不足道是一件好事要做的事)。 为了向后兼容, splitenv PATH CDPATH MANPATH将在启动时执行。 如果用户希望以不同的方式导出它,可以使用string join

所有这一切意味着我们不再需要 \x1e,我们有一个至少有机会被其他程序理解的方案,但是(相当奇特的恕我直言)fish-inside-fish 现在变成fish -c 'splitenv list; count $list' .

问题当然是,如前所述,通常的冒号分隔列表方案无法转义冒号,如果我们想添加一个冒号, string split没有“--unescaped”仅在未转义的分隔符上拆分的选项。

我说得有道理吗?

@faho我认为这个想法很有价值。 旧方案中最糟糕的部分是隐式拆分冒号,这会破坏不应该拆分的变量。 在您的想法中,这(几乎)总是明确的,所以我认为这很安全。

关于转义,根据您找到的链接,不在 PATH 中转义冒号是有意的。 我怀疑 PYTHONPATH、CLASSPATH 等在这方面是否更加一致。 由于您不能在这些路径中使用冒号,因此我们可以选择是否转义它; 但是如果我们转义冒号,我们需要转义反斜杠,我敢打赌你可以在 PATH 中使用反斜杠。 我们可能需要一个“不要逃避”的白名单(呃)。

或者我们不担心它,只是让任何冒号作为分隔符。 我认为我倾向于这样做是为了简单和熟悉其他 shell。

我们仍然面临一些类似列表的变量是用空格分隔的,而另一些是用冒号分隔的问题。 一种可能性是splitenv接受分隔符,记住它,并使用它来重建导出的值:

splitenv --on ' ' LESS
splitenv --on ':' PYTHONPATH

这些调用现在扮演着双重角色,即导入任何现有变量,并标记它是如何导出的。 你怎么看?

另外,有没有办法在不编辑 config.fish 的情况下做到这一点? 也许作为通用变量的一部分?

我们仍然面临一些类似列表的变量是用空格分隔的,而另一些是用冒号分隔的问题。 一种可能性是 splitenv 接受一个分隔符,记住它,并使用它来重建导出的值:

听起来不错。 尽管那时将 splitenv 制作为脚本可能无济于事,因为无论如何我们都需要 C++ 方面的合作。

这些调用现在扮演着双重角色,即导入任何现有变量,并标记它是如何导出的。

现在“splitenv”可能不再是完美的名称了(当然是在我想到它的时候 :laughing: ) - 我也考虑过“listify”。

虽然我不记得我们之前在哪里进行过相关讨论,这让我很烦恼——我想我今晚需要再次搜索这些问题。

用户可以使用例如set SOMEPATH (string split : $SOMEPATH)标记字符串。

string命令在我能找到的任何地方都没有记录。 此外, man string显示了 string(3) 手册页,该手册页记录了 BSD(和 Mac OS X)上的字符串操作函数。

但是,如果我们使用空格导出,则递归调用将显示 1。 正如您所说,我们不依赖于此,但从一致性的角度来看,这很好。

然而,这种行为令人惊讶。 我敢打赌,如果您问 100 个人,当导出具有多个元素的 var 时会发生什么,其中 90 人会说这些值与空格连接作为分隔符。 有些人可能会说逗号或其他字符用作分隔符。 运行env的两个人会说这些值是没有分隔符的,因为除非您通过类似cat -evt的方式过滤输出,否则记录分隔符是不可见的。

出现在 #2090 等问题中

很抱歉,我认为该用户的投诉没有任何价值。 通过显式测试是否已设置 MANPATH 可以轻松解决该问题。 在我看来,鉴于前导冒号和尾随冒号的语义,在任何情况下您都必须这样做。

所以它可能会在其中包含冒号的 $PATH 条目上中断

解决这个问题至少已经晚了三十年。 我们不应该实现冒号的转义(以及转义字符的扩展),因为那将是非标准行为。 直到最近,我还是 20 多年的 UNIX 支持专家。 我从未听过有人抱怨嵌入在 $PATH 或类似变量中的目录中存在冒号是一个问题。

我首选的解决方案是像 splitenv var1 var2 var3 这样的函数

没关系,尽管尚不清楚为什么(未记录的) string split是不够的。 不管我们是否需要一个新功能,我们绝对不应该添加任何新的自动拆分环境变量。 仅有的两个足够常见以保证该行为是 PATH 和 CDPATH (和 MANPATH 因为它已经是特殊情况了)。 如果用户认为 PYTHONPATH 有用,其他变量(如 PYTHONPATH)可以显式拆分。

然而,话虽如此,肯定应该有一种方法来注册给定的 var(例如,PYTHONPATH)在导出时应该将其元素与特定的分隔符字符连接起来。 最自然的方法是通过set命令的新选项。 例如,

set -x -S ':' PYTHONPATH dir1 dir2 dir3

这不会影响 var 在通用 var 数据存储中的存储方式,在该通用 var 数据存储中仍将使用记录分隔符 char,并且在从该数据存储加载时会自动拆分。 需要确定的是注册的用于导出的分隔符​​字符是否也应该影响字符串插值。 我的感觉是应该的。 也就是说,如果上面的“set”命令被执行,那么后续的

echo "PYTHONPATH is $PYTHONPATH"

应该使用冒号而不是空格来连接 PYTHONPATH 的值。 默认分隔符是保留现有语义并最大程度地减少用户意外的空间。 请注意,在该示例中,像 PATH 这样的特殊变量也会使用冒号。 这与当前的行为不兼容,但与新的语义一致并且不那么令人惊讶。 换句话说,为什么 $PATH 的元素在导出的环境中用冒号分隔,但在输出的输出中是空格

echo "PATH is $PATH"

string 命令在我能找到的任何地方都没有记录。 此外,man string 显示了 string(3) 手册页,该手册页记录了 BSD(和 Mac OS X)上的字符串操作函数。

放心,老虎。 它在开发版本中 - 请参阅https://github.com/fish-shell/fish-shell/blob/master/doc_src/string.txt

这很好,尽管尚不清楚为什么(未记录的)字符串拆分是不够的。

我最初的想法是它是一个方便的功能,所以这个操作变得完全微不足道。 随着@ridiculousfish的提议,它变得更多并调整了某种存储,因此在导出时该变量也将加入该字符。 string split只是一个分割字符串的命令——基本上是我们的cut版本。

最自然的方法是通过 set 命令的新选项。

这是另一种选择,尽管我并不完全认同语义。 例如set -S ':' PYTHONPATH 。 这会将 PYTHONPATH 设置为空列表还是仅拆分现有的 PYTHONPATH? 到目前为止,所有设置选项都完成了前者,所以你必须做set -S ':' PYTHONPATH $PYTHONPATH 。 或者我们会让它_不_这样做并且在同一个工具中存在不一致。

换句话说,为什么 $PATH 的元素在导出的环境中用冒号分隔,但在echo "PATH is $PATH"的输出中是空格

这实际上是一个好问题。 当然,您不会期望分隔符出现在for p in $PATH; echo $p; end中,但是将它与每个变量的分隔符 char 结合起来可能是正确的做法。 当然有string join手动完成。

然而,这种行为令人惊讶。 我敢打赌,如果您问 100 个人,当导出具有多个元素的 var 时会发生什么,其中 90 人会说这些值与空格连接作为分隔符。

进行设计调查和钓鱼存在一个普遍的问题。 因为被调查的人经常会了解 bash(以及在较小程度上其他 POSIXy shell),而 fish 的想法是通过放弃至少一些 POSIX 来做一些_更好的事情。

这并不是说它完全没有价值,只是需要牢记——如果我们坚持这些想法,我们就会遇到 bash 的分词行为和 if-fi。

set -S ':' PYTHONPATH会将 PYTHONPATH 设置为空列表还是仅拆分现有的 PYTHONPATH?

它会将其设置为一个空列表。 如果用户想要保留现有值,他们必须明确包含它(见下文)。

我们已经拥有了所有需要的功能,除了在连接给定 var 的数组元素以进行导出或插值时配置要使用的字符(或空字符串)的方法。 如果有人想操作像 PYTHONPATH 这样的变量,他们可以将其视为一个简单的字符串:

set PYTHONPATH "/a/new/dir:$PYTHONPATH"

或者他们可以将其视为一个数组:

set -S ":" PYTHONPATH /a/new/dir (string split ":" $PYTHONPATH)

请注意,我建议在插入字符串时使用拆分/连接字符而不是空格,这提供了一致的行为,无论用户是否将 var 拆分为数组。

我绝对不是建议委员会设计。 这样就存在像zsh这样的疯狂和愚蠢。 我只是指出,当给出两个或多个选项而没有其他理由选择一个时,选择最不会让 shell 用户感到惊讶的选项是最佳选择。 这也是为什么我(目前)反对引入新的命令或行为,例如自动拆分变量(当然,PATH 和 CDPATH 除外)。 这是一种很少发生的事情,通常只在 config.fish 和一些专门的函数中,如 Anaconda 的“激活”脚本。 无论用户是否已经将 var 拆分为他的 config.fish 中的数组,使后者行为正确的方法是始终将其视为需要拆分的字符串。 例如,如果需要修改 PYTHONPATH,它可能会执行以下操作:

# Hypothetical snippet from the Anaconda activate script.
if test (count PYTHONPATH) -gt 1
    set -S ':' PYTHONPATH /activated/python/tree $PYTHONPATH
else
    set PYTHONPATH "/activated/python/tree:$PYTHONPATH"
end

或者,更简单地说,

# Hypothetical snippet from the Anaconda activate script.
set -S ':' PYTHONPATH /activated/python/tree (string split ':' $PYTHONPATH)

是的,这可能会将一个简单的字符串变成一个数组。 但是根据我的规则,在将转换导出和内插到数组时使用-S开关指定的字符在实践中应该无关紧要。 但是,有一个极端情况。 如果用户没有在他的 config.fish 中明确地将 var 转换为数组,然后他运行类似于上面假设的脚本,会发生什么情况。 var 可能成为一个多元素数组,这意味着如果他们随后这样做

for elem in $PYTHONPATH
   echo $elem
end

这不会像用户可能期望的那样,使用冒号分隔目录形式的值只执行一次 for 循环的主体,因为他们不知道假设的“激活”脚本完成的拆分。 我认为我们可以忍受这种情况,因为用户做这样的事情是不正当的。

tl;博士我认为列表应该“记住”它们的分隔符,下面是原因。


我同意上述大部分内容。 一件看起来仍然乏味的事情是上面的命令仍然显得过于冗长。 即,有时用简单的英语描述其中一些命令会更简单。

举个例子(我没有像关注重复事物的数量那样关注长度):

  • 鱼: set -S ':' PYTHONPATH /activated/python/tree (string split ':' $PYTHONPATH)
  • 英语:“将/activated/python/tree附加到PYTHONPATH: -delimited`)”

这里有两个重复的东西: PYTHONPATH和分隔符: 。 应该重复PYTHONPATH可以说是可以的,原因有两个,这两个原因都不适用于分隔符。

  1. 当有人说set PYTHONPATH /activated/python/tree $PYTHONPATH时,不难凭直觉知道发生了什么,因为这与i = 2 + i之类的东西非常相似,这是一个非常熟悉的概念/习语。 (但我们仍然有像+=这样的快捷方式,这就是我在下面提出--append标志的原因。)另一方面,当人们考虑附加到列表时,他们不会考虑拆分并加入分隔符。 他们不会考虑将整个列表转换为其他格式,对其进行真正的操作,然后将其放回原处。 在他们的脑海中,他们很自然地将分隔符理解为分隔符,而不是将其更改为某种“内部”或“首选”分隔符。
  2. 使用普通的set命令来追加保存添加另一个命令来加入两个不同的列表。 另一方面,从一个分隔符转换为另一个分隔符是我们理想情况下不希望用户手动执行的操作,主要是出于上述原因。

相比之下,我建议用另一种方法来指定列表的分隔符:将它与列表无限期地关联起来。 所以上面的例子可以按如下方式完成:

# Changes the delimiter for this list. This might be done in some global config file for common lists as this one.
set -S ':' --no-modify PYTHONPATH
# or, workaround if you don't want to add extra options to set:
set -S ':' PYTHONPATH $PYTHONPATH

# The actual append operation
set --prepend PYTHONPATH /activated/python/tree
# or, workaround if you don't want to add extra options to set:
set PYTHONPATH /activated/python/tree $PYTHONPATH

影响/后续问题/等:

  1. 这与当前的建议相当兼容。 以下是所需的更改:

    • 使分隔符粘贴(可能通过使用另一个变量作为__fish_sep_PYTHONPATH

    • (可选)添加一个我当前调用的标志--no-modify ,它告诉fish 更改列表的分隔符而不更改其内容。 由于上述原因 (1),还可能添加--append--prepend标志。 无论如何,这不是必需的,正如上面的解决方法所示,à la 今天如何在鱼中完成附加和前置。

  2. 在鱼类中,列表必须至少_被视为_一等公民。 这意味着更改定界符应该更改字符串表示,而不是列表表示(除非定界符存在于元素之一中,在这种情况下,拆分是不可避免的)。 例如,如果您将分隔符从,更改为: ,则["0:1", "2"]应该变为["0", "1", "2"]而不是["0", "1,2"] (这就是如果您只是更改分隔符而不更改支持列表的字符串,则会发生这种情况)。 此行为应最大限度地与当前行为的兼容性以及当前存在默认的不可变分隔符这一事实。

这是底线:

  • 这涉及更少的令牌,并且几乎没有重复。
  • 这与许多用户的心理模型相似。 用户以这些术语思考:“前置”、“设置分隔符”、“不要修改”。
  • 这看起来是完成这项任务的唯一正确方法(旧方法现在看起来更加笨拙),因此这些添加不会破坏正交性。
set --no-modify -S : PYTHONPATH
set --prepend PYTHONPATH /activated/python/tree

谢谢@szhu对我的建议的详细评论。 但是,您提出的解决方案存在许多问题。 例如,添加--no-modify选项实际上通过将变量转换为列表来修改变量,因此确实修改了变量。 虽然我拒绝了您提案中的几乎所有内容,但它确实让我想到了一个更直接的解决方案,该解决方案可以解决您的大部分(如果不是全部)观点。 也许应该有一种机制告诉fish 给定的env var 应该总是自动拆分并在给定的标记上重构(例如,“:”或“”)。 这可能被称为自动数组指定,并且在执行时,如果它还不是数组,则任何现有值都将立即被拆分。

可以向set命令添加一个新选项来激活此行为。 但是,我担心这样做是模棱两可的,可能会被解释为定义一个没有值的变量。 将-A 令牌选项添加到set命令是明确的吗? 例如:

set -x -A ':' PYTHONPATH

大概这会在用冒号拆分后立即将任何现有的 PYTHONPATH env var 转换为数组。 相反,它会导致在导出或插入字符串时将值连接到冒号上。 同样,即使在执行该命令时 PYTHONPATH 不存在,也会记住自动数组规范,并且会影响后续使用。 例如,这显然会创建一个数组:

set PYTHONPATH /a/path /b/path /c:/d/path

但是最后一个论点呢? 它应该自动分成两个令牌吗?

请注意,此行为应仅适用于导出的 var,否则会引发错误。 还有一些特殊情况需要说明。 例如,如果原始自动拆分声明包含如下示例中的值怎么办:

set -x -A ':' PYTHONPATH 'hello:goodbye' $PYTHONPATH

这些值是否应该在自动拆分令牌上拆分? 还是应该导致错误并需要在单独的语句中修改值? 无论选择哪种语法,您仍然存在如何处理包含自动拆分标记的值的问题。 细节决定成败。 也就是说,这个提议可能还有其他我没有考虑过的后果。 据我所知,我最初的建议使用更冗长的语法避免了这些问题。

@krader1961 ,感谢您的回复。 但是,您似乎认为我正在将变量从字符串转换为列表。 我认为您误解了 fish 中的一个重要概念:每个变量都是一个字符串列表。 看起来是字符串的变量实际上是长度为 1 的列表。fish 对它们的处理与长度为 0 或 2 或任何其他长度的列表没有区别。

另外,请注意,虽然用于传递环境变量的底层字符串可能会在您更改分隔符时发生变化,但 fish 的优点之一是用户通常根本不需要考虑分隔符。 这就是为什么我建议所有-S选项的作用是指定当它被_exported outside_fish 时,该列表应如何转换为字符串。 -S不应更改鱼内列表表示(除非无法使用目标分隔符表示该列表)。

顺便说一句,这里有一个例子可以说明我的建议是多么干净。 这是将变量$L转换回默认分隔符\x1e的代码。 它对今天可以在鱼中创建的任何变量(任何范围,任何数量的项目)绝对没有影响。

set -S \x1e L $L

还有一件事: --no-modify系列参数只是捷径。 以下是它们的等价物:

| 捷径 | 等效 |
| --- | --- |
| set [other args] --no-modify L | set [other args] L $L |
| set [other args] --prepend L $TOADD... | set [other args] L $TOADD... $L |
| set [other args] --append L $TOADD... | set [other args] L $L $TOADD... |

(我之前已经说过以下内容,但我认为我现在可以更好地解释它。)通过强调这三个论点是多么“愚蠢”,有些人可能会质疑是否需要它们。 有人可能会提到,鱼具有正交性的设计原则。 当所有事物都是正交的时,这意味着对于您想做的任何大任务,选择哪一组功能来完成该任务应该是显而易见的——应该只有一种正确的方法来完成它。 在这里,我确实添加了另一种方式来添加/添加/防止修改到列表,但这只是因为我认为被替换的等价物是不必要的冗长; 它们不应该是附加修改列表的正确方法。 说服自己这一点的一种方法是考虑如何考虑附加到列表。 您可能认为“将$TOADD附加到$L ”而不是“将$L设置为$L $TOADD ”。

让我知道你的想法,如果这对我的提议更有说服力。 (我也很容易误解事情,所以请随时纠正我。)

@szhu我很清楚fish 中的所有变量都是零、一个或多个值的列表。 您显然也没有阅读我之前的评论,其中我明确指出关联的分隔符不应影响内部表示或值如何持久保存到通用数据存储(除了存储分隔符)。 你也没有解决我之前的观点。 考虑你的最后一个例子:

set -S \x1e L $L

如果L已经包含两个或多个值,该怎么办? 大概除了更改关联的分隔符之外别无他法。 在这种情况下$L参数是可选的吗? 还是应该首先将现有数组转换为一个简单的字符串(可能使用现有的分隔符连接)然后在新的分隔符上拆分该字符串? 正如我之前所说,魔鬼在细节中。

最终,成熟的设计人员和维护人员将决定是否应该添加您的--no-modify系列功能,但我投票否决,因为在我看来,相对于他们的成本,他们没有增加足够的价值。

对不起,这个帖子很长,我一定错过了你对上述内容的认可; 很高兴知道我们在同一页上! 我想我也已经解决了您上面的大部分问题,但不是全部。 我将在下面具体解决您的每一个问题。


1. $Lset -S \x1e L $L $ 中是可选的吗?

set的现有行为不会改变。 在当前行为下, set L $L不会更改L并且set L使L成为空列表。 与set -S \x1e L $Lset -S \x1e L相同。

1.1 set -S \x1e L $L只是更改分隔符看起来不是很冗长吗?

轻微地。 这就是为什么我建议使用--no-modify选项作为它的快捷方式。

但如果这条捷径不存在,我的计划也不会失败。 当我们附加列表时,我们已经每天处理这个问题: set PATH ~/.bin $PATH 。 这就是为什么,出于同样的原因,我也建议使用--prepend--append

2. 转换过程将如何进行?

假设我们的旧分隔符是\x1e而我们的新分隔符是 $ : ,并且我们有一个鱼列表["hello", "world"] (导出为hello\x1eworld )。 有两种基本的转换方式(“转换选项”):

  1. 使用["hello", "world"]并将其转换为["hello", "world"] (导出为hello,world
    优点:列表表示不会改变。
  2. 使用hello\x1eworld并将其转换为["hello\x1eworld"] (导出为hello\x1eworld
    优点:导出的值表示不会改变。

请注意,这是从 UI 角度来看,而不是从实现角度来看; 我们正在谈论它对用户的外观。 我将在下一个问题中介绍实现。 _注意:这个答案的其余部分是我上面没有提到的新东西,由你的问题提示。 谢谢!_

鱼内。 首先,如果我们完全在fish中工作,列表是一流的,您永远不必担心分隔符,因此两者都不是必需的。 (同样,“应该”是从用户的角度来看,作为开发人员,我们有责任做到这一点。)

更改 vars 的导出格式。 因此,用户需要更改分隔符的唯一原因是更改读取环境变量的程序的导出字符串。 对于在 fish 中创建的列表,我们肯定会使用转换选项 1 ,因为变量作为列表的含义很重要且定义明确,因此我们需要保留列表表示。

更改 vars 的导入格式。 但是,对于像PATH这样最初在fish 之外创建的环境变量,对于已经有分隔符的列表,我们需要告诉fish 那个分隔符是什么。 为此,我们可以使用转换选项 2

2.1 这将如何实施?

尽管用户不需要知道这一点,但 Fish 实际上将列表存储为字符串。 变量x存储为hello\x1eworld 。 根据我的建议,将有另一个变量__fish_delimiter_x指定变量x的分隔符。 它现在不存在,因此我们使用默认分隔符\x1e

对于转换选项 1:

  1. 在旧的分隔符上拆分变量,从而在实现语言 (C++) 中生成一个真实的列表。
  2. 使用新的分隔符加入列表,产生一个新的字符串。
  3. 将新的分隔符保存到__fish_delimiter_x中。

对于转换选项 1,等效实现:

  1. 在变量中,用新的分隔符替换所有出现的旧分隔符。
  2. 将新的分隔符保存到__fish_delimiter_x中。

对于转换选项 2:

  1. 将新的分隔符保存到__fish_delimiter_x中。

2.2 如果我们需要两个转换选项,用户将如何指定使用哪个?

也许我们可以有两个选项:选项 1 的-D--convert-delimiter和选项 2 的-d--set-delimiter

2.3 我们真的需要这两种选择吗?

在当前的鱼下,我们选择假设我们不会在鱼之外的野外看到\x1e 。 如果我们将其保留为默认分隔符并保留此假设,则转换选项 1足以转换和设置分隔符,我们将不需要转换选项 2 。 (确信这一点的一个简单方法是认识到,如果假设成立,则在转换外部创建的列表时,转换选项 1 的步骤“用新的分隔符替换所有出现的旧分隔符”将无济于事,从而减少整个转换转换选项 2。)


@ridiculousfish ,我也非常感谢您对此的反馈,特别是关于 UI 和实现细节的反馈。 谢谢!

这里似乎有两个问题。 先说第一个吧?

+1 表示真正的数组

鱼真的需要那个分隔符吗? 平 #627

根据我对问题 #2106 的修复重新审视这一点,其中我注意到有两种不兼容的方法可以将变量值的字符串表示形式转换为数组。 其中之一错误地省略了空元素。 核心问题是class env_var_t是基于wcstring而不是 wcstring 的向量。 改变这一点是否值得努力,还有待商榷。

如果您关注此问题,我鼓励您查看并测试 PR #4082。 我决定解决这个问题的最佳方法是“绑定”变量,类似于 zsh 中同名的功能。

似乎围绕这些冒号导出的列表以及该白名单中应包含哪些环境变量的问题尚未确定。 此事的现状如何? 我们能希望这个话题得到最终解决吗? 就在今天,我再次陷入LD_LIBRARY_PATH不在白名单中的陷阱......

总结一下:Unix 环境变量是字符串,所以类似数组的环境变量必须使用分隔符。 大多数使用冒号($PATH); 尽管并非所有人都这样做($LESS)。 我们只想在导入/导出时使用冒号来分隔所有数组(实际上鱼曾经以这种方式工作); 问题是一些平面变量包含冒号($DISPLAY、$SSH_CONNECTION)。

目标是使定界变量在fish的数组支持下自然地工作。 @szhu建议增强set以跟踪分隔符,但 IMO 增强set是错误的附加位置:

  • 设置变量和设置分隔符之间的烦人交互(激励--no-modify )。
  • 关于分隔符如何与变量范围交互的棘手问题。
  • 围绕通用变量的问题。 我们必须教 uvars 记住分隔符,以及--no-modify因为没有办法使用 uvars 将变量设置为其当前值。

在审查时,我赞成@fahosplitenv 想法splitenv name会将现有变量拆分为冒号,就是这样。 这非常简单。 不使用冒号的变量非常少见,我们不需要对它们进行特殊支持。

缺点是 fish 导出的数组将作为冒号分隔的字符串重新导入; 在实践中,我认为这将是罕见的。

如果可以避免的话,我们不应该对用户造成这种splitenv的复杂性。 所以我想更进一步,将冒号白名单扩展到所有名称以 PATH 结尾的环境变量,例如 LD_LIBRARY_PATH、PYTHONPATH 等。在实践中,这应该几乎总是做正确的事情; 任何被它咬伤的人都可以使用string join来修复价值。

所以我的提议(真的是faho的提议):

  • 使用冒号导出所有数组; 不再有 ASCII 记录分隔符。
  • 实现一个splitenv函数,在冒号上拆分变量。 这可以用fish脚本编写。
  • 将冒号分隔的白名单增强到所有以 PATH 结尾的变量。

我相信这将以极简且兼容的方式解决与冒号分隔的数组相关的大部分问题。

关于 splitenv 的想法:

假设我想让 fish 导出一个包含冒号的列表,例如: ["item1", "item:2"] 。 (我认为这种情况并不罕见,尤其是当数组用于存储用户输入时。)

列表会导出为item1:item:2吗? 如果是这样,导出后将无法重新创建原始列表。

此外,为变量设置一个不可变的白名单感觉是错误的,尽管将白名单设置为*?PATH感觉不那么错误。 (这是我建议将分隔符存储为变量的另一个原因——可以通过设置变量来更改白名单。)

@szhu你是对的。 Exported-lists-can't-contain-colons 是 Unix已经遇到的一个问题:

自从 \

所以我对引入相同的鱼限制并不感到太糟糕(仅适用于导出的变量)。

此外,仅在将数组导出到递归调用的鱼实例时才会出现此问题,我认为这种情况很少见。 如果这确实很常见,我们可以将 sidecar 数据附加到另一个变量中,或者在递归调用 fish 时使用不同的数组分隔符。 我的猜测是我们不需要走那么远。

我同意提案效果不佳的边缘情况并不常见,但我担心如果人们遇到它会非常糟糕。

这是一个只是部分设计的示例:

set -x TEST color:red 
set -x TEST2 color:red font:serif
set -x TEST_PATH color:red 
set -x TEST2_PATH color:red font:serif
exec fish
echo $TEST #=> color:red
echo $TEST2 #=> color:red:font:serif
echo $TEST_PATH #=> color red 
echo $TEST2_PATH #=> color red font serif

我可以想象很多新用户在观察上述内容后会感到困惑。

我认为以下行为会更令人愉快:

set -x TEST color:red 
set -x TEST2 color:red font:serif
set -x TEST_PATH color:red 
set -x TEST2_PATH color:red font:serif
exec fish
echo $TEST # color:red
echo $TEST2 # color:red font:serif
echo $TEST_PATH #=> color:red 
echo $TEST2_PATH #=> color:red font:serif

我想听听您和社区对此的看法。

为什么不逃避现有的冒号? 这将保留区别。

逃避对我来说很有意义。

变量是否是数组不会被记住,这是否仍然令人困惑?

set -x TEST2 color:red font:serif
set -x TEST2_PATH color:red font:serif
exec fish
echo $TEST2 # color\:red:font\:serif
echo $TEST2_PATH #=> color:red font:serif

是的。 我的假设是,在路径列表之外,导出数组并不常见。

MANPATH 对双冒号 (::) 有特殊含义 - 请参阅 #2090 - 这会在工作中引发扳手吗?

我还认为,在任何情况下,拥有一个边车变量FISH_ARRAY_VARIABLES=FOO\t:\nBAR\t;\nBAZ\t-\n对于调用 Fish 的实例以再次获取数组变量是一个很好的提示,而不会干扰其他进程并且不需要“我们是现在涉及鱼”检查器..

回复: https ://github.com/fish-shell/fish-shell/issues/436#issuecomment -392409659 @zanchey
我没有详细阅读 #2090,但我相信冒号分隔​​字符串和数组形式之间的转换是完全无缝的(除非冒号 ~ 不出现 ~出现在数组项中)。

要在MANPATH中包含双冒号,只需在应该出现双冒号的位置添加一个空字符串:

$ set -x MANPATH 1 2 '' 3
# Check if it's set
$ bash -c 'echo $MANPATH'
1:2::3

要以冒号开头MANPATH ,只需在开头添加一个空字符串项:

$ set -x MANPATH '' 1 2 3
# Check if it's set
$ bash -c 'echo $MANPATH'
:1:2:3

我没有在这里关注所有内容,但作为用户,我想提倡“无配置”。
我认为set -Ssplitenv是配置的形式。 一些用户会在 fish.config 中执行它们并将 PYTHONPATH 作为数组处理。 其他人不会将 PYTHONPATH 处理为单个冒号分隔的单词。 复制粘贴 stackoverflow 建议和运行脚本将 PYTHONPATH 从一个用户操作到另一个用户将不会总是有效......

一个固定的“如果它以PATH结尾”规则是无配置的,听起来就像你能得到的一样完美:+1:
(我对是否值得向后不兼容没有意见)
是的, set -x TEST2_PATH color:red font:serif将作为color red font serif数组导入,但这就是导出变量的问题。 如果不了解它的工作原理,就无法真正将导出的 var 设置为数组。

是的。 我的假设是,在路径列表之外,导出数组并不常见。

@ridiculousfish在当前的贝壳中可能是正确的,但我想随着鱼获得更多牵引力,用户可能希望利用鱼将列表发送到子鱼壳的能力。 我可以想象最终可能会有管理鱼会话状态的程序/插件(我会在几年后查看此评论以查看是否属实),并且能够普遍地自动反序列化/序列化列表将使该代码更干净,解决方法更少。


有点类似但略有不同的想法:将PATH视为特殊情况是一种不合时宜的边缘情况,用户可能只有在了解 shell 的典型用例时才能理解这种情况。 这限制了 fish 用作通用脚本语言的能力,并限制了一些潜在的未来用例。

@ridiculousfish我认为一种可能的解决方案是将每个环境变量/数组与其自己的分隔符相关联(您可以保留'\x1e'' '':'作为默认值),并且创建环境变量的用户负责选择适当的分隔符以避免冲突。 该命令可能类似于: set --separator ':' TMP 1 2 3 。 因此对于那些众所周知的环境变量,用户只需选择相应的知名分隔符,其他程序也可以识别,可以让fish更兼容更多的程序(如Python)。

对于那些只阅读最近评论的人,请注意@thuzhfset --separator推荐与在此线程中反复提到的set -S推荐相同。 要获得有关该讨论的更多上下文,您可以在此页面上查找set -S

@szhu很抱歉没有注意到之前的set -S 。 这基本上也是我想要的。 我还注意到其他人对这个新选项有几个担忧。 我可以在下面给出我对这些问题的想法(由于fish的设置没有使用-s作为选项,我将使用-s来指代--separator ):

  1. --no-modify确实修改了一些东西。 是的,您应该将名称更改为明确的,例如--change-separator
  2. 有一些角落/棘手的情况。 这基本上是由于语法定义不明确,如果我们对语法进行严格定义,自然可以避免。 例如:

    1. 基本思想:每个 var (列表)在定义时都与自己的分隔符相关联(默认为' ' )。 当这个 var 从字符串创建并转换为字符串时,将使用此分隔符(这在某些语言中很常见,例如 Python 的join()函数)。 导出时或用户想要执行此操作时,var 会转换为字符串。

    2. 如何创建环境变量



      1. set ENV_VAR a b c 。 如果没有-s ,我们选择' '作为默认分隔符。


      2. set -s ':' ENV_VAR 。 在这种情况下,ENV_VAR 被设置为一个空列表。


      3. set -s ':' ENV_VAR a b:c d e:f 。 在这种情况下,编写此代码的用户应该清楚地理解':'是分隔符,并且理解 ENV_VAR 将是一个类似['a b', 'c d e', 'f']的数组,并将导出为'a b:c d e:f' 。 如果您希望导出的 ENV_VAR 以空格开头并以空格结尾怎么办? 您应该使用如下转义符: set -s ':' ENV_VAR \ a b:c d e:f\ 。 然后 ENV_VAR 将为[' a b', 'c d e', 'f ']并将导出为' a b:c d e:f '


      4. set -s ':' ENV_VAR a b:c d e:f $ENV_VAR 。 在这种情况下,这取决于$的工作方式。 如果它被定义为提取 ENV_VAR 的字符串值而不是列表,那么这个命令将与将 $ENV_VAR 替换为从下面的列表转换的字符串值相同,在这种情况下, set -s ':' ENV_VAR a b:c d e:f:$ENV_VAR是可能是您真正想要的(注意:之后的f ); 如果它被定义为提取 ENV_VAR 的变量(它是一个列表而不是字符串),那么这应该成为一个列表扩展操作,就像在 python 中一样。 例如,在后一种情况下,如果ENV_VAR之前是['x', 'y'] ,那么在此操作之后ENV_VAR将变为['a b', 'c d e', 'f', 'x', 'y'] 。 如果ENV_VAR的前一个分隔符不是':'怎么办? 在前一种情况下,您有责任确保您做的是正确的事情,例如,您可能应该通过将原始分隔符更改为':'或将当前分隔符更改为原始分隔符来使用一致的分隔符。 在后一种情况下,这会将这个数组的分隔符从原始数组(无论它是什么)设置为':'



    3. 如何更改分隔符



      1. set --change-separator ':' ENV_VAR 。 如果 ENV_VAR 不存在,则程序应该以 0 以外的错误代码退出。足够简单和明确。



    4. 如何查看分隔符



      1. set --view-separator ENV_VAR



另外,我真的觉得这个问题很明显,很紧迫,是用户的一大痛点,希望这个问题能尽快解决,因为这真的非常影响用户体验。 实际上,除了这么大的鱼,我现在还没有遇到其他(甚至非常小的)问题。

我真的觉得这个问题很明显很紧迫

@thuzhf :我想说你高估了。

一个原因是您在 #5169 中的问题与 $LD_LIBRARY_PATH 相关,但这实际上并不是 fish 中的列表! 您应该将其设置为set LD_LIBRARY_PATH "$LD_LIBRARY_PATH:/some/path" ,就像在其他 shell 中一样。

Fish 自动将三个继承/导出的变量转换为列表:

$PATH、$MANPATH 和 $CDPATH。 并且这个列表在导出时将有一个“:”分隔符。

其他“标准化”变量(如 $LD_LIBRARY_PATH)不应作为 fishscript 中的列表处理,因此您不会遇到此问题。 未标准化的变量您可以随心所欲地处理,因为其他程序无论如何都不会对它们做任何事情,因此分隔符是非关键的。

@faho感谢您的明确解释。 这对我来说真的很有意义。 好的,我可以说这个问题已经为我解决了。

我查看了 #2090 中描述的 MANPATH 问题。 该方案是附加到 manpath 以便它继续使用系统路径。

在 bash 中,有人会将其写为export MANPATH="$MANPATH:/new/path" 。 如果设置了 MANPATH,这将附加到它。 如果未设置,这将在前面加上一个冒号,这是使用系统目录的特定于人的指示。 这种语法在鱼中不起作用; 问题是 MANPATH 是一个数组,因此“$MANPATH”将有空格而不是冒号。

“绑定变量”方法将允许我们将例如fish_manpath作为一个数组,将 MANPATH 镜像为一个冒号分隔的字符串。 这可以完全用鱼脚本构建。 但是,我们希望对所有类似路径的变量执行此操作,而不仅仅是 MANPATH,这将是一个重大的兼容性中断,目前尚不清楚如何管理。 它也有同样的问题,例如 zsh 中的manpath数组变量很难追加,所以不清楚它为什么存在。

我在这里的建议不会使 MANPATH 的情况变得更好或更糟; 我认为要做的事情是平底船,只是有一个简单的故事来附加到 MANPATH,就是这样:

set -q MANPATH || set MANPATH ''
set -x MANPATH $MANPATH /new/path

粘贴到 config.fish 中并不会太痛苦。

我在这里的建议不会使 MANPATH 的情况变得更好或更糟; 我认为要做的事情是平底船,只是有一个简单的故事来附加到 MANPATH,就是这样:

@ridiculousfish :实际上,我一直在考虑更进一步:在“:”上拆分这些特殊变量,也在赋值时将它们与“:”连接起来,而不是在引号扩展中使用空格。

这意味着当您执行set -gx MANPATH "$MANPATH:/new/path"时,fish 会自动执行拆分,从而产生相当于set -gx MANPATH "" /new/path的结果。

现在,这意味着“:”不能出现在 $MANPATH(以及 $PATH 和 $CDPATH)中的路径中,但无论如何他们都不能这样做,因为它会破坏非鱼实用程序!

这也将使我们有一天可以删除特殊处理,因为它添加了一种交叉兼容的处理方式-您只需分配: ,并将其与(string split : -- $MANPATH)一起使用

@faho我对这个想法很感兴趣-用户如何将变量标记为接受这种特殊处理? splitenv会这样做吗?

用户如何将变量标记为接受这种特殊处理?

我的想法实际上是根本不允许标记 - 只需将其作为 $PATH 等的特殊行为即可。 这将使我们在未来的某个时候摆脱列表。

但是,我已经明白,允许其他变量使用它也有助于我们处理其他变量 - 例如,我之前说过我的 $EDITOR 被设置为一个元素( set EDITOR "emacs -nw" )以与外部兼容工具,但如果它是一个列表,鱼会更喜欢它。

所以我可能会默认使用 _space_ 作为分隔符,除非它是类似 PATH 的(并且假设名称以 PATH 结尾可能是可以的)。

splitenv 会这样做吗?

我真的不喜欢为此引入另一个内置函数,所以我可能会选择参数设置选项。

我同意“特殊情况的 PATH/MANPATH/CDPATH 很奇怪;我们需要一个更通用的解决方案”。

我建议我们停止特殊情况下的 PATH/MANPATH/CDPATH。 它们将被(由鱼类最终用户)以它们在其他贝壳中的方式对待。 $PATH (和其他)将是一个带有冒号的字符串(或者用鱼行话来说是一个长度为 1 的列表)。 请注意,我指的是 fish 用户体验,而不是内部如何处理这些事情; 我不知道 fish 内部的实现会是什么样子——我依靠其他人指出那里的任何问题。

诚然,它会有向后不兼容的缺点,但我认为这是值得的,因为它在简单和优雅方面有很大的收获。 我认为它也会解决 #2090 问题。

大家怎么看?

5245 已被合并,所以这似乎解决了。

此页面是否有帮助?
0 / 5 - 0 等级