附加到bash

时间:2017-01-13 06:51:33

标签: bash list

所以我试图获得一个简单的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以某种方式在这个脚本的后台运行?感谢。

1 个答案:

答案 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