迭代遍历一个带有空格的文件列表。

15 浏览
0 Comments

迭代遍历一个带有空格的文件列表。

我想要遍历一个文件列表。这个列表是通过一个find命令得到的,所以我想到了以下解决方案:\n

getlist() {
  for f in $(find . -iname "foo*")
  do
    echo "找到文件: $f"
    # 做一些有用的事情
  done
}

\n这个方法很好,除非文件名中有空格:\n

$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
找到文件: foo_bar_baz.txt
找到文件: foo
找到文件: bar
找到文件: baz.txt

\n有什么办法可以避免按空格切割文件名吗?

0
0 Comments

在上述内容中,我们看到了一个关于遍历带有空格的文件名的问题。问题的原因是在使用for循环遍历文件时,如果文件名中包含空格,会导致循环无法正确识别文件名。解决这个问题的方法是依赖于bash的globbing功能。

解决方法如下:

1. 创建一个名为"test"的文件夹,并进入该文件夹。

2. 使用touch命令创建三个文件,分别命名为"stupid file1"、"stupid file2"和"stupid file 3",这些文件名中包含了空格。

3. 使用ls命令查看当前文件夹中的文件列表,可以看到文件名显示正常。

4. 使用for循环遍历当前文件夹中的所有文件,并打印文件名。在循环中,我们可以看到文件名被正确识别并打印出来。

需要注意的是,作者不确定这种行为是否是默认设置,但是在作者的shopt设置中没有看到任何特殊的设置,因此可以认为这种方法是"安全"的。这种解决方法在OSX和Ubuntu上进行了测试。

0
0 Comments

(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 ''

0
0 Comments

原因:在迭代一个带有空格的文件列表时出现问题。

解决方法:

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”命令。可以通过禁用从标准输入读取的行为来解决此问题。

- 一些回答中提到的方法可能存在一些问题,需要注意修复。

0