我想遍历文件列表。该列表是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


如何避免空格分裂?

评论

基本上,这是何时将引号括在shell变量周围的特定子情况?

#1 楼

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

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


评论


这非常干净。而且让我感觉比与for循环结合更改IFS更好

–德里克
2011年8月18日下午4:13

这将拆分包含\ n的单个文件路径。 OK,这些内容不应该存在,但可以创建:触摸“ $(printf“ foo \ nbar”)“

–奥利·桑德斯(Ollie Saunders)
13-10-17在5:14

为防止对输入进行任何解释(反斜杠,前导和尾随空格),请在读取-r f时使用IFS =。

–mklement0
16年4月2日在17:26

此答案显示了find和while循环的更安全组合。

– moi
16年8月13日在10:40

似乎要指出显而易见的东西,但在几乎所有简单情况下,-exec都会比显式循环:find更干净。 -iname“ foo *” -exec echo“找到的文件:{}” \;。另外,在很多情况下,您可以替换最后一个\;。 with +将大量文件放入一个命令中。

–naught101
16-09-27在0:22

#2 楼

有几种可行的方法可以完成此任务。

如果您希望紧贴原始版本,可以这样操作:

getlist() {
        IFS=$'\n'
        for file in $(find . -iname 'foo*') ; do
                printf 'File found: %s\n' "$file"
        done
}


如果文件名包含以下内容,这仍然会失败它们中的文字换行符,但空格不会破坏它。

但是,不必将IFS弄乱。这是我执行此操作的首选方法:

getlist() {
    while IFS= read -d $'
getlist() {
        find . -iname 'foo*' -print0 | while read -d $'
#!/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 $'q4312078q' -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 $'q4312078q' -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 $'q4312078q' -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"
' -r file ; do printf 'File found: %s\n' "$file" done }
' -r file ; do printf 'File found: %s\n' "$file" done < <(find . -iname 'foo*' -print0) }


如果发现< <(command)语法不熟悉,则应阅读有关进程替换的信息。与for file in $(find ...)相比,此方法的优势在于可以正确处理带有空格,换行符和其他字符的文件。之所以可行,是因为find-print0将使用null(aka while)作为每个文件名的终止符,并且与换行符不同,null不是文件名中的合法字符。

这样做的好处几乎等效的版本

q4312078q

是否保留了while循环主体中的任何变量分配。就是说,如果您如上所述通过管道连接到while,则find ... -print0 | xargs -0的主体位于子外壳中,这可能不是您想要的。

xargs相比,进程替换版本的优势很小:q4312079q如果只需要在文件上打印一行或对文件执行一次操作,则可以使用version版本,但是如果需要执行多个步骤,则循环版本会更容易。

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

q4312078q

评论


接受了您的答案:最完整,最有趣-我不知道$ IFS和<<(cmd)语法。还有一件事对我来说仍然很模糊,为什么$'\ 0'中的$是?非常感谢。

– gregseth
2011年8月12日12:05



+1,但您应添加...,同时IFS = read ...以处理以空格开头或结尾的文件。

–戈登·戴维森(Gordon Davisson)
2011年8月12日下午14:55

流程替代解决方案有一个警告。如果循环内有任何提示(或正在以其他任何方式从STDIN读取),则输入将由您填充到循环中的内容填充。 (也许应该将其添加到答案中?)

– andsens
2013年12月12日18:39

@uvsmtid:这个问题被标记为bash,因此使用bash特定的功能使我感到安全。进程替换不能移植到其他shell(sh本身不可能收到如此重要的更新)。

–sorpigal
15年11月28日在13:48

将IFS = $'\ n'与for结合使用可以防止行内单词拆分,但仍然会使生成的行容易受到干扰,因此这种方法并不完全可靠(除非您也先关闭了对策)。虽然读取-d $'\ 0'可以正常工作,但这有点误导,因为它表明您可以使用$'\ 0'来创建NUL-您不能:在ANSI C引号中的\ 0有效地终止了字符串,因此-d $'\ 0'实际上与-d相同。

–mklement0
16-4-2在17:36



#3 楼

还有一个非常简单的解决方案:依靠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'


请注意,我不确定此行为是默认行为,但我看不到任何特殊设置在我的商店里,所以我会说它应该是“安全的”(已在osx和ubuntu上测试)。

#4 楼

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


评论


附带说明,这仅在您要执行命令时有效。内置的shell无法以这种方式工作。

–亚历山大·吴
2015年2月27日14:52

#5 楼

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


请参阅man xargs

#6 楼

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

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


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

#7 楼

我真的很喜欢for循环和数组迭代,所以我想将这个答案添加到混合中...

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

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


在测试目录中:

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


这会将每个文件列表行添加到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,因此“ $ {arr [$ i]}”是数组的第i个元素。变量周围的引号对于保留空格很重要。

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

$ ls -1
smarter_file1
smarter_file2
smarter_file_3


#8 楼

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

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


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

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

#9 楼

在某些情况下,如果您只需要复制或移动文件列表,则也可以将该列表通过管道传输到awk。
\"" "\"字段周围重要q4312079q(简而言之,您的文件,一个line-list =一个文件)。

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


#10 楼

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

尽管我的问题一直存在于csh中,但我肯定我提出的解决方案可以同时在这两种方法中使用。问题在于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
}


希望这会有所帮助!

#11 楼

作业的另一种解决方案...目标是:


递归地选择/过滤目录中的文件名
处理每个名称(路径中的任何空格。 ..)

#!/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}




评论


谢谢您的建设性发言,但是:1-这是一个实际的问题,2-外壳可能在当时演变了……正如我所假设的所有人一样; 3-以上答案均不能满足pb的直接分辨率,而不会更改问题或分散:-)

–文斯B
19年6月24日在6:49