将IFS设置为空字节不能在命令行中正确分割行

Pan*_*hen 5 bash ifs

~ ls
A B C
Run Code Online (Sandbox Code Playgroud)

猛击(看起来不对)

~IFS=$'\x00' read -a vars < <(find -type f -print0); echo "${vars}"
ABC
Run Code Online (Sandbox Code Playgroud)

在zsh上(看起来不错)

~IFS=$'\x00' read -A vars < <(find -type f -print0); echo "${vars}"
A B C
Run Code Online (Sandbox Code Playgroud)

它是bash的错误吗?

Ini*_*ian 6

在上述两种尝试中,您的逻辑存在很多误解。在bashshell 中,您不能将 NULL 字节的值存储\x00在变量中,无论是特殊变量IFS还是任何其他用户定义的变量。因此,您要求将结果拆分find为 NULL 字节的要求永远不会起作用。因此,您的结果将find作为与 NULL 字节连接的一个长条目存储在数组的第一个索引处。

您可以通过如何将\x00参数传递给程序?中定义的一些技巧来解决在变量中使用 NULL 字节的问题。。您可以使用任何其他自定义字符,IFS就像

IFS=: read -r -a splitList <<<"foo:bar:dude" 
declare -p splitList
Run Code Online (Sandbox Code Playgroud)

读取 NULL 分隔文件的理想方法是将read命令中的分隔符字段设置为读取,直到遇到空字节。

但如果你只是这样做

IFS= read -r -d '' -a files < <(find -type f -print0)
Run Code Online (Sandbox Code Playgroud)

您只读取第一个文件,后跟 NULL 字节,并且该数组"${files[@]}"仅包含一个文件名。您需要循环读取,直到读取最后一个 NULL 字节并且不再读取字符

declare -a array=()
while IFS= read -r -d '' file; do
    array+=( "$file" )
done < <(find -type f -print0)
Run Code Online (Sandbox Code Playgroud)

它发出包含单独数组条目中每个文件的结果

printf '%s\n' "${array[@]}"
Run Code Online (Sandbox Code Playgroud)


Ada*_*atz 5

空字符是非常特殊和POSIX和bash不允许它里面的字符串(它是定义一个字符串的结束,所以$'\x00'$'\000'几乎从来没有工作; Inian的答案在这里甚至连一个解决方法进入空字符,但同样,当您将其分配给变量时,您不能期望它会被正确保留)。看起来zsh并不介意,但bash会介意。

这是一个测试,用于说明在文件名中表示空格,制表符和换行符的问题:

$ touch 'two words' tabbed$'\t'words "two
lines"
$ ls            # GNU coreutils ls displays using bash's $'string' notation
'tabbed'$'\t''words'  'two'$'\n''lines'  'two words'
$ ls |cat       # … except when piped elsewhere
tabbed  words
two
lines
two words
$ find *        # GNU findutils find displays tabs & newlines as questions
tabbed?words
two?lines
two words
$ find * |cat   # … except when piped elsewhere
tabbed  words
two
lines
two words
$ touch a b c   # (more tests for later)
Run Code Online (Sandbox Code Playgroud)

GNU工具非常聪明,并且知道这是一个问题,因此他们想出了一些创新的方法-但它们甚至不一致。ls假设您使用的是bash或zsh(POSIX中存在$'…'文字的语法),并给您一个问号(本身是有效的文件名字符,但它是与任何字符匹配的文件名,因此例如将删除两个文件,就像)。当通过管道传送到另一个命令(例如)时,它们都显示了真相。findrm two?lines tabbed?wordsrm 'two'$'\n''lines' 'tabbed'$'\t''words'cat

GNU / BSD / MacOSX / Busybox查找和xargs

我看到您使用的是GNU扩展:POSIX和BSD / OSX find不允许隐式路径,尽管POSIX find规范确实提到了POSIX,find它不支持POSIX-print0

其他实现还添加了其他方法来解决此问题,特别是-print0 primary使用空字节终止符写入文件名。这里考虑了这一点,但没有采纳。使用空终止符意味着要处理find-print0输出的任何实用程序都必须添加一个新选项来解析现在将要读取的空终止符。

POSIX xargs的符合规范同样缺乏支持-0(有给它没有提及任一),尽管它是由支持xargs在GNU,BSD / OSX和busybox的。

因此,您可以执行以下操作:

$ find . -type f -print0 |xargs -0
./c ./b ./a ./two
lines ./tabbed  words ./two words
Run Code Online (Sandbox Code Playgroud)

但是,您实际上可能需要数组,所以也许我对您的简化问题不满意。

映射文件

您可以mapfile在Bash 4.4和更高版本中使用:

$ mapfile -d '' vars < <(find . -type f -print0)
$ printf '<%s>\n' "${vars[@]}"
<./c>
<./b>
<./a>
<./two
lines>
<./tabbed   words>
<./two words>
Run Code Online (Sandbox Code Playgroud)

包括mapfileread和和readarray(为的同义词mapfile)的某些命令-d ''似乎-d $'\0'(很可能需要引用)接受作为POSIX shell前述无法处理字符串中空字符的解决方法。

mapfile命令仅将输入文件(在这种情况下为标准输入)读取$vars为由空字符分隔的数组。标准输入通过文件描述符通过管道填充,该文件描述符由<(…)该行末尾的进程替换创建,该文件描述符处理find命令的输出。

简而言之:您可能会做的很简单,find … |mapfile …但这会更改范围,并且在管道命令完成时会丢失在其中设置或修改的任何变量。流程替换技巧不会以相同的方式使您陷入困境。

printf命令仅演示了数组的内容。尖括号表示每个项目的开始和结束,因此您不会对换行符,空格或制表符感到困惑。