我使用一个命令解析某些帧的视频文件,并在找到时返回它们的时间码。此刻,我必须执行命令,等待,直到打印到stdout的值到达所需位置,然后使用 Ctrl + C 中止执行。
由于我必须观察过程并在适当的时刻中止执行以获取我需要的信息,我想,我可以通过创建bash脚本在某种程度上自动执行此操作。
我不确定,如果可以用bash完成,因为我完全不知道如何中止与stdout写入的值相关的执行。
命令的输出类似于
0.040000
5.040000
10.040000
15.040000
18.060000
(...)
我试过
until [[ "$timecode" -gt 30 ]]; do
timecode=$(mycommand)
sleep 0.1
done
echo "Result: $timecode"
或
while [[ "$timecode" -le 30 ]]; do
timecode=$(mycommand)
sleep 0.1
done
echo "Result: $timecode"
这两个似乎都导致命令被执行直到它完成,然后正在处理循环的其余部分。但我想在命令执行时评估输出,并根据输出中断执行。
其他信息
该命令无法在流中的某个点停止。它解析整个文件并给出结果,除非发出停止信号。这是我的第一枪。
命令的执行时间很长,因为我解析的文件大约是2GB。由于我不需要文件的所有帧,但只需要在给定的时间码周围只有少数几个,所以在完成之前我永远不会让它执行。
命令的输出因文件而异,因此我无法查找确切的值。如果我知道确切的价值,我可能不会去寻找它。
目的地时间代码 - 在示例中由" -gt 30"指定。 - 对于我将要解析的每个文件都是不同的,所以我必须在脚本工作后将其放入命令行参数中。我还必须确保返回超过执行的最后一个值,但关于最后5个值。对于这两个我已经有了想法。
我完全坚持那个,甚至不知道该怎么去谷歌。
感谢您的投入!
曼努埃尔
通过PSkocik和Kyle Burton的答案,我能够将建议的解决方案集成到我的脚本中。它不起作用,我不明白,为什么。
这里包含提供输出的外部命令的完整脚本:
#!/usr/bin/env bash
set -eu -o pipefail
parser () {
local max="$1"
local max_int
max_int="${max%.*}"
while read tc;
do
local tc_int
tc_int="${tc%.*}"
echo $tc
if (( "$tc_int" >= "$max_int" )); then
echo "Over 30: $tc";
exec 0>&-
return 0
fi
done
}
ffprobe "$1" -hide_banner -select_streams v -show_entries frame=key_frame,best_effort_timestamp_time -of csv=nk=1:p=0:s="|" -v quiet | sed -ne "s/^1|//p" | parser 30
我没有从" echo $ tc"获得任何输出。但ffprobe正在运行 - 我可以看到它在顶部。它一直运行,直到我使用 Ctrl + C 停止脚本。
谢谢Kyle为此付出的巨大努力。我永远不会得出这样的结论。我将ffprobe的命令行改为你的建议
ffprobe "$1" -hide_banner -select_streams v -show_entries frame=key_frame,best_effort_timestamp_time -of csv=nk=1:p=0:s="|" -v quiet | cut -f2 -d\| | parser 30
现在,我在ffprobe运行时获得了结果。但是......你改变命令的方式会返回所有帧,ffprobe找到而不仅仅是关键帧。 ffprobe命令的原始输出类似于
1|0.000000
0|0.040000
0|0.080000
0|0.120000
0|0.160000
0|0.200000
(...)
行开头的0表示:这不是关键帧。 该行开头的1表示:这是一个关键帧。
该脚本仅用于提供视频文件特定时间码周围的关键帧。改变命令的方式,它现在提供视频文件的所有帧,使得结果输出无效。必须对从0开始的所有行进行过滤。
由于我不完全理解,为什么这不适用于sed,我只能尝试通过尝试和错误找到解决方案,促进不同的工具来过滤输出。但如果过滤本身导致问题,我们可能会在这里遇到障碍。
答案 0 :(得分:1)
如果你有进程a
向stdout输出内容并进程b
通过管道读取输出的内容:
a | b
当输出某个项目时,所有b
通常必须杀死a
是关闭其标准输入。
样本b:
b()
{
while read w;
do case $w in some_pattern)exec 0>&-;; esac;
echo $w
done
}
stdin(filedescriptor 0)的关闭将导致生成器进程在尝试进行下一次写入时被SIGPIPE杀死。
答案 1 :(得分:0)
我认为PSkocik的方法是有道理的。我认为您需要做的就是运行mycommand并将其传递到while循环中。如果您将PSkocik的代码放在文件wait-for-max.sh
中,那么您应该能够将其运行为:
mycommand | bash wait-for-max.sh
在上面的评论中与M. Uster合作后,我们提出了以下解决方案:
#!/usr/bin/env bash
set -eu -o pipefail
# echo "bash cutter.sh rn33.mp4"
# From: https://stackoverflow.com/questions/45304233/execute-command-in-bash-script-until-output-exceeds-certain-value
# test -f stack_overflow_q45304233.tar || curl -k -O https://84.19.186.119/stack_overflow_q45304233.tar
# test -f stack_overflow_q45304233.tar || curl -k -O https://84.19.186.119/stack_overflow_q45304233.tar
# test -f rn33.mp4 || curl -k -O https://84.19.186.119/rn33.mp4
function parser () {
local max="$1"
local max_int
# NB: this removes everything after the decimal point
max_int="${max%.*}"
# I added a line number so I could match up the ouptut from this function
# with the output captured by the 'tee' command
local lnum="0"
while read -r tc;
do
lnum="$(( 1 + lnum ))"
# if a blank line is read, just ignore it and continue
if [ -z "$tc" ]; then
continue
fi
local tc_int
# NB: this removes everything after the decimal point
tc_int="${tc%.*}"
echo "Read[$lnum]: $tc"
if (( "$tc_int" >= "$max_int" )); then
echo "Over 30: $tc";
# This closes stdin on this process, which will cause an EOF on the
# process writing to us across the pipe
exec 0>&-
return 0
fi
done
}
# echo "bash version: $BASH_VERSION"
# echo "ffprobe version: $(ffprobe -version | head -n1)"
# echo "sed version: $(sed --version | head -n1)"
# NB: by adding in the 'tee ffprobe.out' into the pipeline I was able to see
# that it was producing lines like:
#
# 0|28.520000
# 1|28.560000
#
#
# changing the sed to look for any single digit and a pipe fixed the script
# another option is to use cut, see below, which is probalby more robust.
# ffprobe "$1" \
# -hide_banner \
# -select_streams v \
# -show_entries frame=key_frame,best_effort_timestamp_time \
# -of csv=nk=1:p=0:s="|" \
# -v quiet 2>&1 | \
# tee ffprobe.out |
# sed -ne "s/^[0-9]|//p" | \
# parser 30
ffprobe "$1" \
-hide_banner \
-select_streams v \
-show_entries frame=key_frame,best_effort_timestamp_time \
-of csv=nk=1:p=0:s="|" \
-v quiet 2>&1 | \
cut -f2 -d\| | \
parser 30
答案 2 :(得分:0)
我的问题的答案终于在PSkocik的帮助和Kyle Burton的大力支持下找到了。谢谢你们两位!
我不知道,可以将脚本中执行的命令输出传递给属于该脚本的函数。这是必要的第一条信息。
我不知道,如何正确评估函数内部的管道信息以及如何从函数内部发出信号,生成值的命令的执行应该终止。
另外,Kyle发现,通过将原始输出传递给sed并将结果数据传递给脚本内部的函数,我做的过滤禁止脚本按设计运行。我仍然不确定,为什么 - 但它确实存在。
生成输出的原始命令现在被管道传送到脚本的内部函数。过滤正在函数内部完成,以避免sed问题。现在一切都按预期工作,我可以继续完成脚本。
这是灵魂的工作代码:
#!/usr/bin/env bash
set -eu -o pipefail
function parser () {
local max="$1"
local max_int
max_int="${max%.*}"
while read tc;
do
#If line is empty, continue
if [ -z "$tc" ]; then
continue
fi
#If first char is 0 (=non-Index Frame), continue
local iskey="${tc:0:1}";
if [ $iskey == "0" ]; then
continue
fi
#Return timecode if intended maximum has been reached
local val="${tc:2:10}"
local tc_int
tc_int="${val%.*}"
if (( "$tc_int" >= "$max_int" )); then
echo "First index frame at/after given Timecode: $tc";
exec 0>&-
return 0
fi
done
}
ffprobe "$1" -hide_banner -select_streams v -show_entries frame=key_frame,best_effort_timestamp_time -of csv=nk=1:p=0:s="|" -v quiet | parser "$2"
用法:
./script.sh "Name of Movie.avi" 30
其中30表示搜索并返回下一个找到的索引帧的时间码。