我想迭代一个文件列表。这个列表是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
我可以做些什么来避免空格分割?
答案 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}