以下脚本play_movie.sh
旨在自动选择一部电影(如果当前目录中只有一部电影)并使用ffplay
.否则,它的目的是向用户显示一系列电影并接受他们的输入(要播放的电影)。
#!/usr/bin/bash
output_a=$(ls -R *.[aA][vV][iI] 2>/dev/null)
output_m=$(ls -R *.[mM][kKpP][vV4]] 2>/dev/null)
output_v=$(ls -R *.[vV][oO][bB]] 2>/dev/null)
all_exts="${output_a}${output_m}${output_v}"
ln_cnt=$(echo "${all_exts}" | wc -l)
if [[ "$ln_cnt" -eq 1 ]]; then
echo -e "Playing: ${all_exts}\n"
ffplay -hide_banner -infbuf -fs -sn -ast a:0 "${all_exts}"
elif [[ "$ln_cnt" -gt 1 ]]; then
printf "Select a file out of the list below"
ls -R *.[aA][vV][iI] 2>/dev/null
ls -R *.[mM][kKpP][vV4] 2>/dev/null
ls -R *.[vV][oO][bB] 2>/dev/null
read line; ffplay -hide_banner -infbuf -fs -sn -ast a:0 "$line"
fi
我对这个脚本的问题是:
if
即使当前目录中有很多电影,脚本也会执行语句的第一个分支。然后我明白了No such file or directory
。- 如果只有一部电影要播放,我会的
No such file or directory
。
我很难依赖ls
过去的输出。是否可以修复此脚本而无需使用find
?
答案1
假设“如果当前目录中只有一个”部分是正确的(IE你只关心当前目录中的文件),你可以避免使用ls
,更不用说find
:
#!/usr/bin/bash -
shopt -s nullglob
set -- *.[aA][vV][iI] *.[mM][kKpP][vV4] *.[vV][oO][bB]
if [[ "$#" -eq 1 ]]; then
printf>&2 "Playing: %s\n\n" "$@"
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- "$@"
elif [[ "$#" -gt 1 ]]; then
printf>&2 "Select a file out of the list below:\n"
printf>&2 "%s\n" "$@"
IFS= read -r line &&
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- "$line"
fi
此设置nullglob
将删除不匹配的 glob,然后将参数设置为所有匹配的文件。
如图所示斯特凡·查泽拉斯的回答,您可以使用它select
来改善用户体验(select
默认使用位置参数,这与上面的使用非常吻合set
):
#!/usr/bin/bash -
shopt -s nullglob
set -- *.[aA][vV][iI] *.[mM][kKpP][vV4] *.[vV][oO][bB]
if [[ "$#" -eq 1 ]]; then
printf>&2 "Playing: %s\n\n" "$@"
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- "$@"
elif [[ "$#" -gt 1 ]]; then
select video; do
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- "$video"
exit
done
fi
答案2
你不能ls
这样使用。这些*.[aA][vV][iI]
是由 shell 扩展的,而不是ls
,-R
forls
是让它R
递归地列出目录的内容,为此你需要向它传递目录的路径,而不是文件,即使这样,它也会输出文件列表(任何非隐藏文件)在这些目录中没有其目录组件,因此ffmpeg
无法找到它们。
另外,除非您使用--zero
GNU 实现的最新版本的选项ls
,否则 的输出ls
根本无法可靠地进行后处理。
要在当前工作目录及以下任意位置查找具有该扩展名列表的文件,您可以使用find
.要存储文件列表(或任何相关的内容),您可以使用数组变量,而不是字符串/标量变量。
使用zsh
而不是bash
在这里会让它变得更容易,因为 zsh 具有(正常工作)递归 globbing 和 glob 限定符,这意味着您还可以将文件列表限制为那些类型常规的。默认情况下,Glob 也会跳过隐藏文件(类似于ls
但不是find
您需要手动跳过的地方):
#! /usr/bin/zsh -
set -o extendedglob
videos=( **/*.(#i)(avi|mkv|mp4|vob)(N-.) )
play() {
print -ru2 Playing $argv
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- $argv
}
case $#videos in
(0) print -u2 No video found; exit 1;;
(1) play $videos;;
(*) print -u2 Select a video:
select video in $videos; do
play $video || exit
break
done;;
esac
使用bash
(GNU shell) + GNU find
,您可以执行以下操作:
#! /usr/bin/bash -
readarray -td '' videos < <(
LC_ALL=C find . \
-regextype posix-extended \
-name '.?*' -prune -o \
-iregex '.*\.(avi|mkv|mp4|vob)' \
-xtype f \
-printf '%P\0' |
sort -z
)
shopt -u xpg_echo
play() {
echo>&2 -E Playing "$@"
ffplay -hide_banner -infbuf -fs -sn -ast a:0 -- "$@"
}
case ${#videos[@]} in
(0) echo>&2 No video found; exit 1;;
(1) play "${videos[@]}";;
(*) echo>&2 Select a video:
select video in "${videos[@]}"; do
play "$video" || exit
break
done;;
esac
其他一些注意事项:
echo -e
如果文件名包含字符,则会中断\
。在这里你做的不是希望e
扩展 scape 序列,因此使用-E
forecho
或-r
forprint
。如果您希望它打印额外的换行符,请将其添加到echo
literal 或 with的最后一个参数中$'\n'
。另请注意,仅当 或未设置时才bash
接受选项(并且它们在某些系统和/或某些环境中默认设置)。一般来说,最好避免使用,我仍然在这里使用它,因为它能够轻松地打印与空格连接的参数(这对于使用 来说更麻烦,并且 bash 仅作为一个可加载插件,很少有系统在默认情况下包含它bash )并且我们知道 shell 是,因此该函数可用于一次播放多个视频。echo
posix
xpg_echo
echo
printf
print
bash
play
- 提示和用户消息通常应发送至 stderr (fd 2)。
select
例如,这就是 's 输出的位置。 - 读取一行的语法是
IFS= read -r line
, 不是read line
。但无论如何,文件路径不必由一行组成,因为换行符与文件名中的任何字符一样有效。 - 请注意,您
*.[mM][kKpP][vV4]
也会匹配files.mv4
(不是m4v
)。