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

时间:2019-03-06 03:25:43

标签: bash ifs

~ ls
A B C

在bash上(看起来不正确)

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

在zsh上(看起来不错)

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

这是bash的错误吗?

3 个答案:

答案 0 :(得分:4)

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

您可以通过How to pass \x00 as argument to program?中定义的一些技巧来解决在变量中使用NULL字节的问题。您可以为IFS使用其他任何自定义字符,尽管就像

IFS=: read -r -a splitList <<<"foo:bar:dude" 
declare -p splitList

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

但是如果您只是这样做

IFS= read -r -d '' -a files < <(find -type f -print0)

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

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

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

printf '%s\n' "${array[@]}"

答案 1 :(得分:4)

null character非常特殊,POSIX and bash do not allow it inside strings(它是字符串末尾的定义,所以$'\x00'$'\000'很漂亮几乎永远无法工作; Inian's answer在这里甚至链接到workaround for entering the null character,但同样,当您将其分配给变量时,您也无法期望将其正确保留)。看起来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)

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

GNU / BSD / MacOSX / Busybox查找和xargs

我看到您正在使用GNU扩展:POSIX和BSD / OSX find不允许隐式路径,尽管{{3 }}确实提到了它:

  

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

尽管find在GNU,BSD / OSX和busybox中受-print0支持,但POSIX find spec同样缺乏对-0的支持。

因此,您可能可以这样做:

xargs

但是,您实际上可能需要数组,所以我可能不适合您的简化问题。

地图文件

您可以在Bash 4.4和更高版本中使用$ find . -type f -print0 |xargs -0 ./c ./b ./a ./two lines ./tabbed words ./two words

mapfile

某些命令,包括$ mapfile -d '' vars < <(find . -type f -print0) $ printf '<%s>\n' "${vars[@]}" <./c> <./b> <./a> <./two lines> <./tabbed words> <./two words> mapfilereadreadarray的同义词)接受mapfile,就好像它是{{1} },可能是 [需要引用] 作为POSIX shell前述无法处理字符串中空字符的解决方法。

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

简而言之:您认为您可以简单地做<(…),但是那会改变范围,并且当管道命令完成时,您在其中设置或修改的任何变量都会丢失。流程替换技巧不会以相同的方式困住您。

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

答案 2 :(得分:0)

如果您的 xargs 支持 -0 并且您只想在与 Bourne 兼容的 shell(sh、dash、bash、zsh、busybox shell 等)循环中迭代以空分隔的字符串列表你可以这样做:

find . -type f -print0|xargs -0 sh -c 'while test $# -gt 0;do echo "$1";shift;done' sh

或使用“for”循环:

find . -type f -print0|xargs -0 sh -c 'for i;do echo "$i";done' sh