迭代遍历一个带有空格的文件列表。
在上述内容中,我们看到了一个关于遍历带有空格的文件名的问题。问题的原因是在使用for循环遍历文件时,如果文件名中包含空格,会导致循环无法正确识别文件名。解决这个问题的方法是依赖于bash的globbing功能。
解决方法如下:
1. 创建一个名为"test"的文件夹,并进入该文件夹。
2. 使用touch命令创建三个文件,分别命名为"stupid file1"、"stupid file2"和"stupid file 3",这些文件名中包含了空格。
3. 使用ls命令查看当前文件夹中的文件列表,可以看到文件名显示正常。
4. 使用for循环遍历当前文件夹中的所有文件,并打印文件名。在循环中,我们可以看到文件名被正确识别并打印出来。
需要注意的是,作者不确定这种行为是否是默认设置,但是在作者的shopt设置中没有看到任何特殊的设置,因此可以认为这种方法是"安全"的。这种解决方法在OSX和Ubuntu上进行了测试。
(Iterate over a list of files with spaces)这个问题的原因是因为文件名中包含空格导致无法正确迭代文件列表。解决方法有以下几种:
1. 使用IFS变量和for循环来迭代文件列表,但是这种方法无法处理文件名中包含换行符的情况。
2. 使用while循环和IFS变量,并且通过< <(command)
的方式将命令的输出作为文件输入,这样可以正确处理文件名中包含空格或换行符的情况。
3. 使用while循环和IFS变量,并通过管道将find命令的输出作为文件输入,但这种方式可能会导致在循环体内部的变量赋值无法保留。
4. 使用find命令的-print0选项和xargs命令的-0选项来处理文件名中包含空格或换行符的情况,但这种方式只适合对文件进行单个操作。
文章整理如下:
有几种可行的方法可以解决这个问题。如果你想保持原来的版本,可以这样做:
getlist() { IFS=$'\n' for file in $(find . -iname 'foo*') ; do printf 'File found: %s\n' "$file" done }
这种方法仍然无法处理文件名中包含换行符的情况,但是可以正确处理包含空格的文件名。
然而,不必要地修改IFS变量是没有必要的。这是我首选的方法:
getlist() { while IFS= read -d $'\0' -r file ; do printf 'File found: %s\n' "$file" done < <(find . -iname 'foo*' -print0) }
如果你对< <(command)
语法不熟悉,你应该阅读一下关于进程替换的文章。与for file in $(find ...)
相比,这种方法可以正确处理文件名中包含空格、换行符和其他特殊字符的情况。这是因为使用find
命令的-print0
选项会使用null字符作为每个文件名的终止符,而null字符不是文件名中的合法字符。
与几乎相同的版本相比,这种方法的优势在于循环体内的任何变量赋值都会被保留。也就是说,如果你将命令输出通过管道传递给while
循环,那么while
循环体就处于一个子shell中,可能不是你想要的结果。
与find ... -print0 | xargs -0
相比,使用进程替换的方法优势很小:如果你只需要打印一行或对文件进行单个操作,xargs
版本是可以接受的,但如果需要执行多个步骤,则循环版本更容易。
这里还有一个漂亮的测试脚本,可以让你了解不同解决方案之间的差异:
#!/usr/bin/env bash dir=/tmp/getlist.test/ mkdir -p "$dir" cd "$dir" touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\ 'foo with'$'\n'newline 'foo with trailing whitespace ' # while with process substitution, null terminated, empty IFS getlist0() { while IFS= read -d $'\0' -r file ; do printf 'File found: '"'%s'"'\n' "$file" done < <(find . -iname 'foo*' -print0) } # while with process substitution, null terminated, default IFS getlist1() { while read -d $'\0' -r file ; do printf 'File found: '"'%s'"'\n' "$file" done < <(find . -iname 'foo*' -print0) } # pipe to while, newline terminated getlist2() { find . -iname 'foo*' | while read -r file ; do printf 'File found: '"'%s'"'\n' "$file" done } # pipe to while, null terminated getlist3() { find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do printf 'File found: '"'%s'"'\n' "$file" done } # for loop over subshell results, newline terminated, default IFS getlist4() { for file in "$(find . -iname 'foo*')" ; do printf 'File found: '"'%s'"'\n' "$file" done } # for loop over subshell results, newline terminated, newline IFS getlist5() { IFS=$'\n' for file in $(find . -iname 'foo*') ; do printf 'File found: '"'%s'"'\n' "$file" done } # see how they run for n in {0..5} ; do printf '\n\ngetlist%d:\n' $n eval getlist$n done rm -rf "$dir"
这是一个非常好的答案,但你仍然在几个地方缺少IFS=
。
你可以使用不同的文件描述符来进行读写操作。
当然!我没有想到过,谢谢。一些搜索表明,在这种情况下,mkfifo可能是创建新文件描述符的最佳工具,对吗?
这个示例中存在一些问题。我有一个更完整的示例,稍后我会添加链接,以便在有机会发布时添加链接。
< <(cmd)
语法正是我正在寻找的。但是,使用#!/bin/sh
shebang运行脚本会失败。这意味着它不是可移植的,虽然这不是一个大问题,但它解释了为什么它迄今为止不被广泛知道和使用。
这个问题的解决方案有一个限制。如果循环体内部有任何提示(或以其他方式从标准输入读取),输入将被你输入到循环中的内容填充。(也许应该将这个问题添加到答案中?)
这是一个非常好的答案,但在几个地方仍然缺少IFS=
。
将IFS=$'\n'
与for
结合使用可以防止行内单词拆分,但生成的行仍然受到文件名中的通配符影响,所以这种方法不是完全可靠的(除非你首先关闭通配符)。虽然read -d $'\0'
可以工作,但它稍微有点误导,因为它暗示你可以使用$'\0'
来创建NUL字符-你不能:在ANSI C-quoted string中,\0
实际上是一个终止符,所以-d $'\0'
实际上等于-d ''
。
原因:在迭代一个带有空格的文件列表时出现问题。
解决方法:
1. 使用基于行的迭代代替基于单词的迭代:
find . -iname "foo*" | while read f do # ... 循环体 done
2. 为了防止输入的解释(反斜杠、前导和尾随空格),可以使用`IFS= while read -r f`。
3. 使用`-exec`比显式循环更简洁:`find . -iname "foo*" -exec echo "File found: {}" \;`。
4. 对于具有文字换行符的文件名,行级迭代无效,可以使用NUL分隔流来正确迭代文件。
此外,还有一些其他的注意事项:
- 在循环体中读取标准输入会导致丢失一些输入。
- 某些命令(如ffmpeg)会从标准输入中读取,可能会干扰循环中使用的“read”命令。可以通过禁用从标准输入读取的行为来解决此问题。
- 一些回答中提到的方法可能存在一些问题,需要注意修复。