遍历带有空格的文件列表

新手上路,请多包涵

我想遍历文件列表。这个列表是 find 命令的结果,所以我想出了:

 getlist() {
  for f in $(find . -iname "foo*")
  do
    echo "File found: $f"
    # do something useful
  done
}

没关系,除非文件的名称中有空格:

 $ ls
foo_bar_baz.txt
foo bar baz.txt

$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt

我能做些什么来避免空间分割?

原文由 gregseth 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

您可以将基于单词的迭代替换为基于行的迭代:

 find . -iname "foo*" | while read f
do
    # ... loop body
done

原文由 martin clayton 发布,翻译遵循 CC BY-SA 3.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 ...) 的优点是正确处理带有空格、换行符和其他字符的文件。 This works because find with -print0 will use a null (aka \0 ) as the terminator for each file name and, unlike newline, null 不是文件名中的合法字符。

与几乎等效版本相比的优势

getlist() {
        find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
                printf 'File found: %s\n' "$file"
        done
}

是否保留了while循环主体中的任何变量赋值。也就是说,如果您通过管道连接到 while ,那么 while 的主体可能不是您想要的。

进程替换版本优于 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"

原文由 sorpigal 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题