所以我试图获得一个简单的bash脚本来连续读取目录并更新要通过命令播放的文件列表。但是,我在思考其中的逻辑时遇到了一些麻烦。我需要做的是将目录中的当前项目放入列表中,让目录中的每个项目都通过程序运行,当新项目进入时,只需将其附加到列表中即可。我试图使用inotifywait
,但似乎无法想到正确的逻辑。我可能需要它在后台运行,因为在这些文件上运行的进程将在再次读取inotifywait
之前运行,此时它将不会拾取任何已添加的新文件,因为它只检查什么时候运行这是代码,所以希望它更有意义。
#!/bin/bash
#Initial check to see if files are converted.
if [ ! -d "/home/pi/rpitx/converted" ]; then
echo "Converted directory does not exist, cannot play!"
exit 1
fi
CYAN='\e[36m'
NC='\e[39m'
LGREEN='\e[92m'
#iterate through directory first and act upon each item
for f in $FILES
do
echo -e "${CYAN}Now playing ${f##*/}...${NC}"
#Figure out a way to always watch directory even when it is playing
inotifywait -m /home/pi/rpitx/converted -e create -e moved_to |
while read path action file; do
echo -e "${LGREEN}New file found: ${CYAN}${file}${NC}"
FILES+=($file)
done
# take action on each file. $f store current file name
sudo ./rpitx -m RF -i "${f}" -f 101100
done
exit 0
所以举个例子。如果rpitx
当前正在播放某个内容,并且文件已转换,则它不会获取最新文件并将其添加到列表中,也不会成功,因为它始终在阅读。有没有办法让inotifywait
以某种方式在这个脚本的后台运行?感谢。
答案 0 :(得分:2)
这实际上是100%完美的难题,但是可能会非常接近。
很容易获取目录中的所有文件,并且很容易使用inotifywait
来迭代地获知放入目录中的新文件。问题是让两者保持一致。如果在处理完所有文件(甚至只是刚刚列出)之前,inotifywait
尚未启动,那么您可能会错过在列表和inotifywait
调用之间创建的新文件。另一方面,如果首先启动inotifywait
,则在调用inotifywait
之后创建的文件和当前文件列表的提取将被列出两次。
由于过滤重复项比通知孤儿更容易,推荐的方法是第二种。
作为第一个近似值,我们可以忽略重复问题,假设漏洞窗口非常短,因此可能不太可能发生。这简化了代码,但跟踪和消除重复并不困难:例如,我们可以将每个文件名存储为关联数组中的键,如果密钥已经存在则忽略该文件。
我们需要三个流程:一个执行inotifywait
;一个用于生成初始文件列表;和一个处理每个文件,因为它被识别。所以代码的基本结构将是:
list_new_files |
{ list_existing_files; pass_through; } |
while read action file; do
handle -r "$action" "$file"
done
请注意,第二个进程首先生成现有文件,然后调用pass_through
,它从标准输入读取并写入标准输出,从而传递list_new_files
发现的文件。由于管道具有有限的容量,list_existing_files
的执行可能会阻塞几次(如果存在大量现有文件并且处理它们需要很长时间),所以当pass_through
最终获得时执行后,它可能会有相当多的排队输入通过。这并不重要,除非第一个管道也填满,如果创建了大量新文件,就会发生这种情况。只要inotifywait
在写入时被阻止而不会丢失通知,这仍然无关紧要。 (这可能实际上是一个问题,因为我的系统上inotifywait
的联机帮助页包含在" BUGS"部分中的注释,"假设inotify事件队列永远不会溢出。 "我们可以通过插入另一个小心缓冲inotifywait
输出的进程来解决问题,但除非你打算用大量文件填充目录,否则不应该这样做。)
现在,让我们依次检查每个功能。
list_new_files
可能只是从原始脚本调用inotifywait
:
inotifywait -m / home / pi / rpitx / converted -e create -e moved_to
列出现有文件也很简单。这是一个简单的解决方案:
printf "%s\n" /home/pi/rpitx/converted/*
但是,这将打印出完整的文件路径,这与inotifywait
的输出不同。为了使它们相同,我们cd
进入目录以进行列表。由于我们实际上可能不想更改工作目录,因此我们通过围绕括号内的命令来使用子shell:
( cd /home/pie/rpitx/converted; printf "%s\n" *; )
printf
只是在一个单独的行上打印其参数。由于glob-expansions 不字拆分或递归全局扩展,因此可以安全地防止文件名中的空格或元字符,除了换行符。带换行符的文件名非常少见;现在,我将忽略这个问题,但我会在最后说明如何处理它。
即使进行了上述更改,这两个命令的输出也不兼容:第一个输出每行输出三个(目录,操作,文件名),第二个输出一个(文件名)。在下面的列表中,您将看到我们如何将格式修改为printf
并为inotifywait
引入格式,以便使输出完全兼容,并使用"操作&#34 ;对于设置为EXISTING
的现有文件。
pass_through
可能只是cat
,这就是我在下面对它进行编码的方式。但是,它必须在线缓冲模式下运行;否则,什么都不会发生,直到"足够"文件由list_existing_files
编写。在我的系统上,此配置中的cat
运行正常;如果这对您不起作用或者您不想依赖它,您可以将其明确地写为一个读取循环:
pass_through() {
while read -r line; do echo "$line"; done
}
最后,handle
本质上是原始帖子中的代码,但稍作修改以考虑新格式,并使用操作EXISTING
执行正确的操作。
# Colours. Note the use of `$'...'` to actually store the code,
# thereby avoiding the need to later reinterpret backslash sequences
CYAN=$'\e[36m'
NC=$'\e[39m'
LGREEN=$'\e[92m'
converted=/home/pi/rpitx/converted
list_new_files() {
inotifywait -m "$converted" -e create -e moved_to --format "%e %f"
}
# Note the use of ( ) around the body instead of { }
# This is the same as `{( ... )}'; it makes the `cd` local to the function.
list_existing_files() (
cd "$converted"
printf "EXISTING %s\n" *
)
# Invoked as `handle action filename`
handle() {
case "$1" in
EXISTING)
echo "${CYAN}Now playing ${2}...${NC}"
;;
*)
echo "${LGREEN}New file found: ${CYAN}${file}${NC}"
;;
esac
sudo ./rpitx -m RF -i "${f}" -f 101100
}
# Put everything together
list_new_files |
{ list_existing_files; cat; } |
while read -r action file; do handle "$action" "$file"; done
如果我们认为文件名中可能包含换行符,该怎么办?有两个" safe"可以用来分隔文件名的字符,因为它们不能出现在文件名中。一个是 / ,它显然可以出现在一个路径中,但不能出现在一个简单的文件名中,这就是我们在这里使用的。另一个是NUL
字符,它根本不会出现在文件名中,但有时候处理起来有点烦人。
通常,遇到此问题,我们会使用NUL
,但这取决于我们使用的各种实用程序,允许使用NUL
而不是换行符分隔数据。但inotifywait
的情况并非如此,它始终在通知行后输出换行符。所以在这种情况下,使用 / 似乎更简单。首先我们修改格式:
inotifywait -m "$converted" -e create -e moved_to --format "%e %f/"
printf "%s/\n" *
现在,当我们阅读这些行时,我们需要阅读,直到找到以 / 结尾的行(并记住将其删除)。 read
不允许使用双字符行终止符,因此我们需要自己累积行:
while read -r action file; do
# If file doesn't end with a slash, we need to read another line
while [[ file != */ ]] && read -r line; do
file+=$'\n'"$line"
done
# Remember to remove the trailing slash
handle "$action" "${file%/}"
done