使用空格迭代文件列表

时间:2011-08-12 10:58:24

标签: linux bash shell

我想迭代一个文件列表。这个列表是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

我可以做些什么来避免空格分割?

11 个答案:

答案 0 :(得分:228)

您可以使用基于行的迭代替换基于单词的迭代:

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

答案 1 :(得分:148)

有几种可行的方法可以实现这一目标。

如果您想密切关注原始版本,可以这样做:

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)语法不熟悉,请阅读process substitution。这个优于for file in $(find ...)的优点是可以正确处理带有空格,换行符和其他字符的文件。这是有效的,因为带有find的{​​{1}}将使用-print0(又名null)作为每个文件名的终止符,并且与换行符不同,null不是合法字符。文件名。

相对于几乎等效的版本

的优势
\0

是否保留了while循环体中的任何变量赋值。也就是说,如果您如上所述管道getlist() { find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do printf 'File found: %s\n' "$file" done } ,则while的正文位于子shell中,这可能不是您想要的。

流程替换版本优于while的优势很小:find ... -print0 | xargs -0版本没问题,如果只需要打印一行或对文件执行单个操作,但是如果需要执行多个步骤循环版本更容易。

编辑:这是一个不错的测试脚本,因此您可以了解解决此问题的不同尝试之间的区别

xargs

答案 2 :(得分:30)

还有一个非常简单的解决方案:依赖bash globbing

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"
$ ls
stupid   file 3  stupid file1     stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid   file 3'
file: 'stupid file1'
file: 'stupid file2'

请注意,我不确定这种行为是否属于默认行为,但我不会在我的购物中看到任何特殊设置,所以我会说它应该是&#34;安全&#34; (在osx和ubuntu上测试)。

答案 3 :(得分:13)

find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"

答案 4 :(得分:11)

find . -name "fo*" -print0 | xargs -0 ls -l

请参阅man xargs

答案 5 :(得分:6)

由于您没有使用find进行任何其他类型的过滤,因此您可以在bash 4.0之后使用以下内容:

shopt -s globstar
getlist() {
    for f in **/foo*
    do
        echo "File found: $f"
        # do something useful
    done
}

**/将匹配零个或多个目录,因此完整模式将匹配当前目录或任何子目录中的foo*

答案 6 :(得分:2)

我非常喜欢循环和数组迭代,所以我想我会将这个答案添加到混合...

我也喜欢marchelbling的愚蠢文件示例。 :)

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"

在测试目录中:

readarray -t arr <<< "`ls -A1`"

这会将每个文件列表行添加到名为arr的bash数组中,并删除任何尾随换行符。

让我们说我们想给这些文件更好的名字......

for i in ${!arr[@]}
do 
    newname=`echo "${arr[$i]}" | sed 's/stupid/smarter/; s/  */_/g'`; 
    mv "${arr[$i]}" "$newname"
done

$ {!arr [@]}扩展为0 1 2所以&#34; $ {arr [$ i]}&#34;是数组的i th 元素。变量周围的引号对于保留空格非常重要。

结果是三个重命名的文件:

$ ls -1
smarter_file1
smarter_file2
smarter_file_3

答案 7 :(得分:1)

find有一个-exec参数,该参数循环查找结果并执行任意命令。例如:

find . -iname "foo*" -exec echo "File found: {}" \;

这里{}代表找到的文件,并将其包装在""中,使所得的shell命令可以处理文件名中的空格。

在许多情况下,您可以用\;替换最后一个\+(启动一个新命令),这会将多个文件放在一个命令中(虽然不一定要一次全部存储,有关更多详细信息,请参见man find

答案 8 :(得分:0)

在某些情况下,如果您只是需要复制或移动文件列表,也可以将该列表传输到awk。
重要的是\"" "\"字段$0(简而言之,您的文件,一个行列表=一个文件)。

find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }'

答案 9 :(得分:0)

好-我在Stack Overflow上的第一篇文章!

尽管我的问题一直存在于csh而不是bash中,但是我敢肯定,我所提出的解决方案在这两者中都可以使用。问题在于shell对“ ls”返回的解释。我们可以通过简单地使用*通配符的shell扩展来从问题中删除“ ls”,但是如果当前(或指定的文件夹)中没有文件,这会产生“ no match”错误-为此,我们只是将扩展扩展为包括点文件,因此:* .*-自文件起,这将始终产生结果。和..将始终存在。因此,在csh中,我们可以使用此构造...

foreach file (* .*)
   echo $file
end

如果您想过滤掉标准点文件,那很容易...

foreach file (* .*)
   if ("$file" == .) continue
   if ("file" == ..) continue
   echo $file
end

该线程第一篇文章中的代码将这样写:-

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

希望这会有所帮助!

答案 10 :(得分:0)

另一种完成任务的人...

目标是:

  • 在目录中递归选择/过滤文件名
  • 处理每个名称(路径中的任何空格...)
#!/bin/bash  -e
## @Trick in order handle File with space in their path...
OLD_IFS=${IFS}
IFS=$'\n'
files=($(find ${INPUT_DIR} -type f -name "*.md"))
for filename in ${files[*]}
do
      # do your stuff
      #  ....
done
IFS=${OLD_IFS}