bash:迭代目录内容直到条件匹配的最佳实践
我有以下情况: 我想检查目录bash:迭代目录内容直到条件匹配的最佳实践,bash,loops,iteration,Bash,Loops,Iteration,我有以下情况: 我想检查目录$1中的文件,直到其中一个符合我的条件 详细说明:我想测试目录是否包含音频文件。一旦找到第一个音频文件,process\u audio\u dir;如果目录中没有音频文件,将执行处理\u noaudio 到目前为止,我的解决方案是: if [[ -z $(file -b "$1"/* | grep -i audio) ]]; then echo "there are no audio files"; process_noaudio else echo "at
$1
中的文件,直到其中一个符合我的条件
详细说明:我想测试目录是否包含音频文件。一旦找到第一个音频文件,process\u audio\u dir
;如果目录中没有音频文件,将执行处理\u noaudio
到目前为止,我的解决方案是:
if [[ -z $(file -b "$1"/* | grep -i audio) ]]; then
echo "there are no audio files"; process_noaudio
else
echo "at least one audio file"; process_audio_dir
fi
file-b
告诉我文件的文件类型
通过使用set-x
查看它,我的猜测是,这将在所有文件上运行file-b
,将结果放在一行中,并将该行变灰以进行匹配。(也许这是一个错误的假设)
我希望有一个循环,直到它找到第一个音频文件(一个足以匹配条件)并在那里停止/中断,或者,如果没有音频文件,则继续执行处理\u noaudio
我有一种感觉,while/until将是实现这一目标的关键,但我无法理解
检查目录中每个文件直到第一次匹配的方法(您首选的“最佳实践”、“最优雅”、“成本最低”、“最快”)是什么?最安全的方法可能是直接迭代glob结果,这样您就不会受到包含特殊字符的文件名的攻击:
for path in "$1"/*; do
if file -b -- "$path" | grep -qi audio; then
printf 'Found an audio file %s\n' "$path"
process_audio_dir
exit
fi
done
# since we didn't exit above, most be no audio files
printf "Didn't find any audio files\n"
process_noaudio
或者,如果您不想退出,您可以设置一个标志,指示您找到了它,并在循环后检查它,并且只要在if
中使用中断
即可在找到循环后退出循环
您将grep
应用于所有文件
结果的输出的原因是glob首先展开,然后运行命令,例如
file -b dir/file1 dir/file2 dir/file3 ...
然后,该命令的输出将被馈送到grep
My solution将glob放在命令的“外部”,因此我们将在每个文件上单独运行它。当然,多次启动文件
会带来更大的开销,所以我现在还不清楚哪一个更有效。这可能取决于有多少文件,第一个音频文件通常在列表的下面有多远,诸如此类
正如评论中所提到的,从
find
或ls
中迭代打印的文件名结果是危险的,因为这些结果可能会出现分词和潜在的全局搜索,具体取决于您的操作方式。通常建议使用上面的for
循环。有关更多信息,请参见POSIX C API中的/这是以流的形式增量读取目录内容的最灵活的方法。但是,这些函数到Bash没有这样的映射
ls
是在Bash中列出目录内容的主要方式。您可以使用ls
执行如下操作,以将列表作为流进行处理(但我非常确定ls
是否会缓冲列表。ls
支持排序,这需要缓冲):
另一个常用工具是find
。逐步查找工作。以下内容适用于“迭代目录内容直到条件匹配”的用例,是比ls
更好的方法。这只是打印第一个找到的文件名。调整以完全适应条件匹配时要执行的操作:
find -maxdepth 1 -type f -exec "bash" -c "file -b '{}' | grep -qi audio" ";" -print -quit
效率低,但兼容
在这里,我们正在对每个单独的名称执行一个shell管道,该管道运行file
,然后调用grep
检查其结果。这显然是低效的,但由于-exec
在其运行的shell命令返回非零退出代码时失败,find
仍将在第一个结果的早期退出,其中grep
返回真实值(从而允许运行-print
和-quit
操作)
高效,但仅GNU
shopt-s nocaseglob#启用不区分大小写的匹配
而IFS=read-r-d“”文件名&&IFS=read-r类型;做
如果[[$type=*音频*];然后
break#退出循环,名称在“$filename”中,类型在“$type”中
fi
done<我以为我做了,但是我删除了(额外的)代码标签。我猜这是自动评论?谢谢。代码很好读。哪个平台?如果我们只需要支持GNU文件
,那么这可以以一种既不牺牲效率也不牺牲正确性的方式来实现;是的,这就是我的目的。我可以对$中的每个_文件执行for循环,可能是(查找“$1”-类型f);do file-b$每个文件| grep-i音频;如果[匹配音频文件]执行x;继续;否则,你就不必担心了;继续;fi
之类的。我想知道“标准”或最有效的方法是什么?@badlands,使用for
循环以这种方式迭代find
输出是一种反模式;请参阅条目#1.BTW——为什么“假设目录中没有太多文件”<代码>for
循环不受argv长度限制。@CharlesDuffy啊,我实际上不确定该部分,所以感谢修复,我现在就更新!唯一让我感到困惑的是,$1
中命名的目录以破折号开头——不太可能,但可能;在展开“$path”之前传递“$code>”将修复此问题。否则,我能找到的唯一反对意见是与效率相关的(每个文件运行一次file
),在不牺牲正确性的情况下避免这些反对意见需要GNUIMS。这里给出的建议非常有害:以编程方式使用ls
输出,因为它的格式没有很好地指定,无法以一种可以可靠地反转为其文本内容的方式准确地表示所有文件名(因此,可以通过open()
-type调用使用)。出于类似的原因,自动使用find
find -maxdepth 1 -type f -exec "bash" -c "file -b '{}' | grep -qi audio" ";" -print -quit
find "$1" -maxdepth 1 -type f \
-exec sh -c 'file -b -- "$1" | grep -qi audio' _ {} \; \
-print \
-quit
shopt -s nocaseglob # enable case-insensitive matching
while IFS= read -r -d '' filename && IFS= read -r type; do
if [[ $type = *audio* ]]; then
break # exit the loop with the name in "$filename" and the type in "$type"
fi
done < <(find "$1" -type f -maxdepth 1 -exec file -b -0 -- '{}' +)
echo "Found file $filename of type $type"