我有由 生成的数据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 - $var
orprint -r -- $var
(尽管在 ksh 中,您需要print -r - "$var"
)。 print -rC1 -- $list
print -rl -- $list
通常比在 olumn 上打印列表r
aw更好,1
C
因为如果不传递任何参数,后者会打印空行。