Bash脚本无法继续读取文件的下一行

4

我有一个Shell脚本,将执行的命令输出保存到CSV文件中。它从一个格式为以下内容的Shell脚本中读取要执行的命令:

ffmpeg -i /home/test/videos/avi/418kb.avi /home/test/videos/done/418kb.flv
ffmpeg -i /home/test/videos/avi/1253kb.avi /home/test/videos/done/1253kb.flv
ffmpeg -i /home/test/videos/avi/2093kb.avi /home/test/videos/done/2093kb.flv

您可以看到每一行都是一个ffmpeg命令。然而,脚本只执行第一行。刚才它几乎执行了所有的命令。由于某种原因,有一半命令丢失了。我编辑了包含这些命令的文本文件,但现在它只会执行第一行。这是我的bash脚本:

#!/bin/bash
# Shell script utility to read a file line line.
# Once line is read it will run processLine() function

#Function processLine
processLine(){
line="$@"

START=$(date +%s.%N)

eval $line > /dev/null 2>&1 

END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)

echo "$line, $START, $END, $DIFF" >> file.csv 2>&1
echo "It took $DIFF seconds"
echo $line
}

# Store file name
FILE=""

# get file name as command line argument
# Else read it from standard input device
if [ "$1" == "" ]; then
   FILE="/dev/stdin"
else
   FILE="$1"
   # make sure file exist and readable
   if [ ! -f $FILE ]; then
    echo "$FILE : does not exists"
    exit 1
   elif [ ! -r $FILE ]; then
    echo "$FILE: can not read"
    exit 2
   fi
fi
# read $FILE using the file descriptors

# Set loop separator to end of line
BAKIFS=$IFS
IFS=$(echo -en "\n\b")
exec 3<&0
exec 0<$FILE
while read line
do
    # use $line variable to process line in processLine() function
    processLine $line
done
exec 0<&3

# restore $IFS which was used to determine what the field separators are
BAKIFS=$ORIGIFS
exit 0

感谢您的任何帮助。

更新2

问题出在ffmpeg命令上,而不是shell脚本。但是我应该像Paul指出的那样只使用"\b"。我还正在使用Johannes更短的脚本。


将IFS设置为“\n\b”不意味着它会在空格处断开吗? - Paul Tomblin
我刚刚尝试了"\n",但它仍然只读取第一行。我不太擅长shell脚本。 - Abs
在 processLine 中加入一些 echos,以查看传递了什么以及何时完成处理。也许是 ffmpeg 没有返回。 - Paul Tomblin
我认为你是正确的。我将通过手动执行一些命令来检查ffmpeg正在做什么。 - Abs
现在你知道为什么要立即将东西放在配置管理控制下了,这样你就可以回到工作版本了! - Jonathan Leffler
5个回答

4
我认为这样做应该是相同的,而且似乎是正确的:
#!/bin/bash

CSVFILE=/tmp/file.csv

cat "$@" | while read line; do
    echo "Executing '$line'"
    START=$(date +%s)
    eval $line &> /dev/null
    END=$(date +%s)
    let DIFF=$END-$START

    echo "$line, $START, $END, $DIFF" >> "$CSVFILE"
    echo "It took ${DIFF}s"
done

no?


根据转换的不同,ffmpeg可能会非常耗时。您可以通过不过滤标准错误来获取进度状态。 - mouviciel
是的,FFmpeg在某些情况下确实需要一些时间,但是shell脚本不应该等待命令完成吗? - Abs
是的,它应该并且我认为它会;-) 到底发生了什么? - Johannes Weiss
我刚刚发现我的许多命令无法执行,原因是“未知格式不支持作为输入像素格式”。我必须找到可以实际执行的好样本视频! - Abs
'&>'会被当作'&'(后台运行)和'>'(输出重定向至/dev/null)吗?或者它是某种bash的扩展? - Jonathan Leffler
显示剩余5条评论

4

ffmpeg 读取 STDIN 并将其耗尽。解决方法是使用以下命令调用 ffmpeg:

 ffmpeg </dev/null ...

点击此处查看详细解释:http://mywiki.wooledge.org/BashFAQ/089

更新:

自ffmpeg版本1.0以来,还有-nostdin选项,因此可以使用它代替上述方法:

 ffmpeg -nostdin ...

1
或者使用带有“-nostdin”选项的ffmpeg进行调用。 - Magne
@Magne:确实,这是现在更好的选择。感谢您将那个旧答案重新提出,以便我可以更新它。 - mivk

2

我刚遇到了同样的问题。

我认为这种行为是由ffmpeg造成的。

我的解决方法:

1) 在你的ffmpeg命令行结尾加一个"&"

2) 由于现在脚本不会等待ffmpeg进程完成, 我们必须防止我们的脚本启动多个ffmpeg进程。 我们通过延迟循环传递来实现这个目标,同时还有至少 一个正在运行的ffmpeg进程。

#!/bin/bash

cat FileList.txt |
while read VideoFile; do
    <place your ffmpeg command line here> &
    FFMPEGStillRunning="true"
    while [ "$FFMPEGStillRunning" = "true" ]; do
        Process=$(ps -C ffmpeg | grep -o -e "ffmpeg" )
        if [ -n "$Process" ]; then
            FFMPEGStillRunning="true"
        else
            FFMPEGStillRunning="false"
        fi 
        sleep 2s
    done
done

1

除非您计划在循环后从标准输入读取内容,否则无需保留和恢复原始的标准输入(尽管看到您知道如何做是好的)。

同样地,我不认为有必要完全修改IFS。毫无疑问,在退出之前恢复IFS的值是没有必要的 - 您正在使用真正的shell,而不是DOS BAT文件。

当您执行以下操作时:

read var1 var2 var3

Shell会将第一个字段分配给$var1,第二个字段分配给$var2,并将行的其余部分分配给$var3。如果只有一个变量(例如您的脚本),整行都会进入变量中,就像您想要的那样。

在进程行函数内部,您可能不希望丢弃执行命令的错误输出。您可能需要考虑检查命令的退出状态。使用错误重定向的echo是不寻常且过度的。如果您足够确定命令不会失败,请忽略错误。命令是否“喋喋不休”?如果是,请务必扔掉聊天记录。如果不是,也许您不需要扔掉标准输出。

整个脚本应该诊断出当它被给予多个要处理的文件时,因为它会忽略多余的文件。

您可以通过仅使用以下内容来简化文件处理:

cat "$@" |
while read line
do
    processline "$line"
done

cat 命令会自动报告错误(并在错误后继续执行),并处理所有输入文件,如果没有剩余参数,则读取标准输入。在变量周围使用双引号意味着它作为单个单位传递(因此未解析为单独的单词)。

使用 datebc 是有趣的 - 我以前从未见过。

总的来说,我会考虑类似以下的东西:

#!/bin/bash
# Time execution of commands read from a file, line by line.
# Log commands and times to CSV logfile "file.csv"

processLine(){
    START=$(date +%s.%N)
    eval "$@" > /dev/null
    STATUS=$?
    END=$(date +%s.%N)
    DIFF=$(echo "$END - $START" | bc)
    echo "$line, $START, $END, $DIFF, $STATUS" >> file.csv
    echo "${DIFF}s: $STATUS: $line"
}

cat "$@" |
while read line
do
    processLine "$line"
done

1
我会在 eval 前后添加 echos,以查看它将要评估的内容(以防它将整个文件视为一个长行)和之后(以防其中一个 ffmpeg 命令需要很长时间)。

是的,它读取某些命令时出现错误。例如,有些命令被打断了:“os/mp4/4457kb.mp4 /home/test/videos/done/4457kb.flv”——我已经检查过文件中有一个换行符! - Abs

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