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

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

我有由 生成的数据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

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

cs^@:           text/x-shellscript
da^@:           text/x-shellscript
dns-test^@:     text/x-shellscript

但是,当使用下标来匹配第一个时单词,输出是第一个特点反而。所以,

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

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

c
d
d

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

for f in "${files[@]}"; do
  print "${f[(w)1]}"
done

这有效:

cs
da
dns-test

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

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

答案1

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

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

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

$ ls
':\0:'$'\n'':\1:'  'a'$'\n''b'
$ file --print0 --mime-type -- ./* | sed -n l
./:\\0:$
:\\1:\000: application/x-xz$
./a$
b\000:       application/zip$

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

在这里,你可以这样做:

typeset -A mime_type
file --print0 --mime-type --no-pad --separator '' ./**/* |
  while
    IFS= read -rd $'\0' file &&
      IFS=' ' read -r type
  do
    mime_type[$file]=$type
  done

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

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

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


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

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

non_empty_NUL_separated_fields=( ${(0)scalar} )
NUL_separated_fields=( "${(0@)scalar}" )

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

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

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

before_first_NUL=${scalar%%$'\0'*}

或者 IFS 分割:

IFS=$'\0'
NUL_separated_fields=( $=scalar )

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

first=${scalar/$'\0'*}

对您的代码的一些评论:

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

相关内容