同步执行Shell脚本

3

一个修改过的shell脚本可以将音频文件从FLAC格式转换为MP3格式。计算机使用四核CPU。该脚本通过以下方式运行:

./flac2mp3.sh $(find flac -type f)

这段代码将flac目录中的FLAC文件(文件名中不能有空格)转换为mp3目录中的MP3文件(与flac在同一层级)。如果目标MP3文件已经存在,则跳过该文件。
问题是有时候脚本的两个实例几乎同时检查同一个MP3文件是否存在,导致生成损坏的MP3文件。
您如何多次运行脚本(即每个核心运行一次),而无需在每个命令行中指定不同的文件集,并且不会覆盖工作?
更新-最小竞态条件
脚本使用以下锁定机制:
  # Convert FLAC to MP3 using tags from flac file.
  #
  if [ ! -e $FLAC.lock ]; then
    touch $FLAC.lock
    flac -dc "$FLAC" | lame${lame_opts} \
      --tt "$TITLE" \
      --tn "$TRACKNUMBER" \
      --tg "$GENRE" \
      --ty "$DATE" \
      --ta "$ARTIST" \
      --tl "$ALBUM" \
      --add-id3v2 \
      - "$MP3"
    rm $FLAC.lock
  fi;

然而,这仍然存在竞态条件。
8个回答

6
"lockfile"命令可为shell脚本提供您想要实现的功能,而不会出现竞争条件。该命令由procmail团队专门为此类目的编写,并在大多数BSD / Linux系统上可用(因为procmail可在大多数环境中使用)。
您的测试变成了这样:
lockfile -r 3 $FLAC.lock
if test $? -eq 0 ; then
  flac -dc "$FLAC" | lame${lame_opts} \
    --tt "$TITLE" \
    --tn "$TRACKNUMBER" \
    --tg "$GENRE" \
    --ty "$DATE" \
    --ta "$ARTIST" \
    --tl "$ALBUM" \
    --add-id3v2 \
    - "$MP3"
fi
rm -f $FLAC.lock

或者,您可以使锁定文件无限重试,这样您就不需要测试返回代码,而是可以测试输出文件以确定是否运行flac。


2
应该将 rm -f $FLAC.lock 放在 if 语句内部。否则,您可能会删除其他进程创建的锁文件。 - Ivan Kovtun
同时,if test $? -eq 0也是一种反模式。 - tripleee

4
如果您没有lockfile并且无法安装它(有几个实现版本),则一个强大而可移植的原子互斥锁是mkdir。 如果要创建的目录已经存在,则mkdir将失败,因此您可以检查该目录;当创建成功时,您可以保证没有其他协作进程与您的代码同时处于关键部分。
if mkdir "$FLAC.lockdir"; then
    # you now have the exclusive lock
    : critical section
    : code goes here
    rmdir "$FLAC.lockdir"
else
    : nothing? to skip this file
    # or maybe sleep 1 and loop back and try again
fi

如果您所在的平台可靠地提供flock并需要一种性能更好的替代lockfile,为了完整起见,也可以查找flock


1

将输出发送到一个带有唯一名称的临时文件中,然后将该文件重命名为所需的名称。

flac -dc "$FLAC" | lame${lame_opts} \
      --tt "$TITLE" \
      --tn "$TRACKNUMBER" \
      --tg "$GENRE" \
      --ty "$DATE" \
      --ta "$ARTIST" \
      --tl "$ALBUM" \
      --add-id3v2 \
      - "$MP3.$$"
mv "$MP3.$$" "$MP3"

如果竞态条件偶尔泄漏到您的文件锁定系统中,最终输出仍将是一个进程的结果。

这显然避免了在竞争情况下数据混乱的不愉快,但如果两个进程对同一文件进行编码,然后它们的结果被更多或更少地立即覆盖,仍然无法防止大量冗余工作。 - tripleee

1
你可以实现对正在处理的FLAC文件加锁的机制。类似这样:
if (not flac locked)
  lock flac
  do work
else
  continue to next flac

这个Yahoo Answer似乎对如何在shell脚本中执行此操作有一个不错的想法。 http://answers.yahoo.com/question/index?qid=20061011215658AAbuBfB - michael.d.snider
你需要修改shell脚本以包含这个逻辑。 - Byron Whitlock
这与Byron的回答有相同的竞态条件问题。你需要在一个单一原子操作中进行检查和锁定,以避免出现此问题。 - tripleee

0

写一个Makefile怎么样?

ALL_FLAC=$(wildcard *.flac)
ALL_MP3=$(patsubst %.flac, %.mp3, $(ALL_FLAC)
all: $(ALL_MP3)
%.mp3: %.flac
        $(FLAC) ...

然后执行

$ make -j4 all

0

你可能想要阅读这里的内容:http://meta.stackexchange.com/questions/57497/limits-for-self-promotion-in-answers - nkjt

0

为了锁定文件进程,您可以创建一个与同名文件相同的文件,并在其后添加.lock扩展名。

在开始编码之前,请检查.lock文件是否存在,并可选择确保锁定文件的日期不太旧(以防进程死亡)。如果不存在,请在编码开始之前创建它,并在编码完成后将其删除。

您还可以使用flock锁定文件,但这只适用于在c中调用flock()并写入文件然后关闭和解锁的情况。对于shell脚本,您可能正在调用另一个实用程序来编写文件。


不行,这里有一个竞态条件 -- 如果文件出现在你检查和创建它之间,它就无法达到预期目的。你需要一步实现原子检查和创建 - tripleee

0
在Bash中,可以设置noclobber选项以避免文件覆盖。
帮助设置| egrep 'noclobber | -C'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接