用空格遍历文件列表
我想迭代一个文件列表。 这个列表是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
我能做些什么来避免在空间上的分裂?
你可以用一个基于行的replace基于单词的迭代:
find . -iname "foo*" | while read f do # ... loop body done
有几个可行的方法来完成这一点。
如果你想紧贴原始版本,可以这样做:
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)
语法不熟悉,你应该阅读关于进程replace 。 这for file in $(find ...)
中的文件的优点是具有空格,换行符和其他字符的文件被正确处理。 这是有效的,因为用-print0
find
将会使用null
(aka \0
)作为每个文件名的终结符,而不像换行符,null在文件名中不是合法的字符。
这几乎相当于版本的优势
getlist() { find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do printf 'File found: %s\n' "$file" done }
是否在while循环的主体中的任何variables赋值被保留。 也就是说,如果你像上面那样pipe道,那么这个时候的身体就在一个子壳里, while
这个子壳可能并不是你想要的。
进程replace版本的优点是find ... -print0 | xargs -0
find ... -print0 | xargs -0
是最小的:如果你需要的只是打印一行或者对文件执行一个单独的操作,那么xargs
版本就没有问题,但是如果你需要执行多个步骤,循环版本会更容易。
编辑 :这是一个很好的testing脚本,所以你可以得到解决这个问题的不同尝试之间的区别的想法
#!/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"
还有一个非常简单的解决scheme:依靠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'
请注意,我不确定这种行为是默认的行为,但我没有在我的shopt中看到任何特殊的设置,所以我会去说,它应该是“安全的”(在osx和Ubuntu上testing)。
find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"
find . -name "fo*" -print0 | xargs -0 ls -l
看到man xargs
。
由于您没有使用find
进行任何其他types的筛选,因此您可以使用以下bash
4.0:
shopt -s globstar getlist() { for f in **/foo* do echo "File found: $f" # do something useful done }
**/
将匹配零个或多个目录,所以完整模式将匹配当前目录或任何子目录中的foo*
。
在某些情况下,如果您只需要复制或移动文件列表,则可以将该列表pipe理为awk。
重要的\"" "\"
字段$0
(简而言之,您的文件,一个行列表=一个文件)。
find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }'
我真的很喜欢循环和数组迭代,所以我想我会添加这个答案的混合…
我也喜欢marchelbling愚蠢的文件的例子。 🙂
$ mkdir test $ cd test $ touch "stupid file1" $ touch "stupid file2" $ touch "stupid file 3"
在testing目录里面:
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,所以“$ {arr [$ i]}”是数组的第 i 个元素。 variables周围的引号对于保存空间很重要。
结果是三个重命名的文件:
$ ls -1 smarter_file1 smarter_file2 smarter_file_3