如何可靠地捕获此脚本中“ls”的输出?

如何可靠地捕获此脚本中“ls”的输出?

以下脚本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

我对这个脚本的问题是:

  1. if即使当前目录中有很多电影,脚本也会执行语句的第一个分支。然后我明白了No such file or directory
  2. 如果只有一部电影要播放,我会的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-Rforls是让它R递归地列出目录的内容,为此你需要向它传递目录的路径,而不是文件,即使这样,它也会输出文件列表(任何非隐藏文件)在这些目录中没有其目录组件,因此ffmpeg无法找到它们。

另外,除非您使用--zeroGNU 实现的最新版本的选项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 序列,因此使用-Eforecho-rfor print。如果您希望它打印额外的换行符,请将其添加到echoliteral 或 with的最后一个参数中$'\n'。另请注意,仅当 或未设置时才bash接受选项(并且它们在某些系统和/或某些环境中默认设置)。一般来说,最好避免使用,我仍然在这里使用它,因为它能够轻松地打印与空格连接的参数(这对于使用 来说更麻烦,并且 bash 仅作为一个可加载插件,很少有系统在默认情况下包含它bash )并且我们知道 shell 是,因此该函数可用于一次播放多个视频。echoposixxpg_echoechoprintfprintbashplay
  • 提示和用户消息通常应发送至 stderr (fd 2)。select例如,这就是 's 输出的位置。
  • 读取一行的语法是IFS= read -r line, 不是read line。但无论如何,文件路径不必由一行组成,因为换行符与文件名中的任何字符一样有效。
  • 请注意,您*.[mM][kKpP][vV4]也会匹配files.mv4(不是m4v)。

相关内容