zsh 中 NUL 分隔词的下标变量扩展

Pou*_*ash 5 zsh

我有 生成的数据file,它标识一个目录(或多个目录)中所有文件的 mime 类型。结果是一个列表,命令位于左侧,mime 类型位于右侧。默认情况下,这两个值(或字符串?)由冒号 (:) 分隔。我想用 NUL 字符将命令与其 mime 类型分隔开。所以,

files=( ${(f)"$(file --print0 --mime-type **/*)"} )

这似乎有效。

print -l ${files[@]}给出:

cs:           text/x-shellscript
da:           text/x-shellscript
dns-test:     text/x-shellscript
Run Code Online (Sandbox Code Playgroud)

或者,print -l ${(V)files[@]}给出:

cs^@:           text/x-shellscript
da^@:           text/x-shellscript
dns-test^@:     text/x-shellscript
Run Code Online (Sandbox Code Playgroud)

但是,当使用下标来匹配第一个单词时,输出是第一个字符。所以,

for f in "${files[@]}"; do
  print "${f[(pws:\0:)1]}"
done
Run Code Online (Sandbox Code Playgroud)

这仅打印每个数组元素的第一个字符(由 输出的每一行file):

c
d
d
Run Code Online (Sandbox Code Playgroud)

如果我省略 的规范(ps:\0:),从而使用默认的空格分隔,那么我确实会得到每个元素的第一个单词。所以,

for f in "${files[@]}"; do
  print "${f[(w)1]}"
done
Run Code Online (Sandbox Code Playgroud)

这有效:

cs
da
dns-test
Run Code Online (Sandbox Code Playgroud)

但我不想依赖空白。我想使用像 NUL 这样更安全的分隔符。请注意,我还尝试了下标[(pws:\000:)1],但这也只产生了第一个字符

使用下标时如何识别由 NUL 分隔的单词?

Sté*_*las 5

文件路径可以包含换行符。这就是为什么一些报告文件或文件相关信息的实用程序允许您使用 NUL 而不是换行符来分隔记录,因为 0 是文件路径中唯一不能出现的字节值。

\n

如果您像使用 一样在换行符上分割输出${(f)"$(file...)"},那么您就达不到目的了。

\n

file有点奇怪,因为它不使用 NUL 作为其记录分隔符,而是使用它来分隔其输出记录中的文件路径(其定义仍然松散)。

\n
$ ls\n\':\\0:\'$\'\\n\'\':\\1:\'  \'a\'$\'\\n\'\'b\'\n$ file --print0 --mime-type -- ./* | sed -n l\n./:\\\\0:$\n:\\\\1:\\000: application/x-xz$\n./a$\nb\\000:       application/zip$\n
Run Code Online (Sandbox Code Playgroud)\n

因此,它的记录<file-path><NUL><colon><some-spaces><mime-type><newline>使得解析变得不必要地更加困难(如果文本包含换行符,甚至是不可能的,幸运的是,--mime-type不应该是这种情况)。

\n

在这里,你可以这样做:

\n
typeset -A mime_type\nfile --print0 --mime-type --no-pad --separator \'\' ./**/* |\n  while\n    IFS= read -rd $\'\\0\' file &&\n      IFS=\' \' read -r type\n  do\n    mime_type[$file]=$type\n  done\n
Run Code Online (Sandbox Code Playgroud)\n

其中第一个read以 NUL 作为-d分隔符读取 NUL 分隔的文件路径,第二个读取换行符分隔的 mime 类型(通过 IFS 处理修剪前导空格)。

\n

--no-pad变成<some-spaces>单个空格,--separator \'\'删除冒号,所以我们有<file-path><NUL><space><mime-type><newline>

\n

在循环中,我们填充$mime_type关联数组,因此您可以使用 获取给定文件的类型$mime_type[./given/file]或使用 列出给定类型的文件print -rC1 -- ${(k)mime_type[(R)text/plain]}

\n
\n

至于为什么$scalar[(pws[\\0])1]不在 NUL 上拆分,这是当前版本的 zsh 中的一个错误。该行为与建议 zsh 无法转义 NUL 的行为相同$array[(pws[])1],否则通常会这样做,因此最终会在空字符串上进行拆分。缺少转义会影响其他分隔符,例如所有包含字节值 0x83 到 0xa2 的分隔符(例如\xc3\xa1在 UTF-8 中编码为 0xc3 0xa1)。

\n

如果没有s[separator],我发现它会按$IFS字符分割(不是空格,另一个错误,这次是一个文档), NUL 处于默认值$IFS,所以你可以这样做:IFS=$\'\\0\'; first_non_empty_NUL_separated_field=$scalar[(w)1],但你也可以使用0( 的缩写ps[\\0])参数扩展标志

\n
non_empty_NUL_separated_fields=( ${(0)scalar} )\n
Run Code Online (Sandbox Code Playgroud)\n
NUL_separated_fields=( "${(0@)scalar}" )\n
Run Code Online (Sandbox Code Playgroud)\n

(进而first=$NUL_separated_fields[1])。

\n

或者一口气first=${${(0)scalar}[1]}

\n

或者您可以使用${scalar%%pattern}Korn shell 运算符:

\n
before_first_NUL=${scalar%%$\'\\0\'*}\n
Run Code Online (Sandbox Code Playgroud)\n

或者 IFS 分割:

\n
IFS=$\'\\0\'\nNUL_separated_fields=( $=scalar )\n
Run Code Online (Sandbox Code Playgroud)\n

或者 ksh93 风格${param/pattern[/replacement]}

\n
first=${scalar/$\'\\0\'*}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

对您的代码的一些评论:

\n
    \n
  • 在 中file --print0 --mime-type **/*,如果任何文件路径以 开头-,则该路径将被视为file. 您可以将其更改为避免这种情况,但如果当前工作目录中file --print0 --mime-type -- **/*调用了文件,则仍然无法正常工作。-使用./**/*可以避免这两个问题(以及进一步的问题)。
  • \n
  • @,无论是作为参数扩展标志还是 as[@]$@仅在引用时才真正有意义,以防止删除空元素。在列表上下文中与or or${files[@]}相同,并扩展到数组中的非空元素。请注意,在类似 Korn 的 shell 中,您还需要引号,以防止空删除,同时也防止 split+glob。类似 Korn 的 shell(与大多数其他 shell 或语言相反)也需要大括号,并且单独扩展到索引 0 的元素,而不是所有元素。$files$files[@]$files[*]$files
  • \n
  • 使用 时print,您通常希望使用-r选项,否则print会进行一些反斜杠处理,并且您几乎总是希望使用---选项分隔符,否则会引入命令注入漏洞。print $var是一个 ACE 漏洞。就像在 Korn shell 中一样,您需要print -r - $varor print -r -- $var(尽管在 ksh 中,您需要print -r - "$var")。
  • \n
  • print -rC1 -- $list通常比在 olumn 上print -rl -- $list打印列表raw更好1 C,因为如果不传递任何参数,后者会打印空行。
  • \n
\n

  • @terdon,这不是解决方法。OP 在换行符上分割然后在 NUL 上分割每个结果行的方法是错误的。现在,为什么 `$scalar[(pws:\0:)1]` 不在 NUL 上拆分看起来像是一个错误。您始终可以使用 `IFS=$'\0'` 和 `$scalar[(w)1]` 或 `${${(0)scalar}[1]}` 作为解决方法。 (2认同)