为什么这些函数的执行会跳出这个 while 循环?

为什么这些函数的执行会跳出这个 while 循环?

以下脚本旨在修剪当前工作目录中的所有媒体文件。

#!/usr/bin/bash

trimmer() {
  of=$(echo "${if}"|sed -E "s/(\.)([avimp4kvweb]{3,3}$)/\1trimmed\.\2/")
  ffmpeg -hide_banner -loglevel warning -ss "${b}" -to "${ddecreased}" -i "${if}" -c copy "${of}"
  echo "Success. Exiting .."
}

get_ddecreased() {
  duration="$(ffprobe -v quiet -show_entries format=duration -hide_banner "${if}"|grep duration|sed -E s/duration=\([0-9]*\)\..*$/\\1/)"
  echo ${duration} 
  ddecreased="$(echo "${duration} - ${trimming}"|bc -l)"
  echo ${ddecreased} 
}

rm_source() {
  echo -e "Remove ${if}?[Y/n]?"
  read ch
  if [[ "${ch}" == 'y' ]]; then
    rm "${if}"
  fi 
}


echo "How much of the beginning would you like to trim?"
read b
echo "How much of the end would you like to trim?"
read trimming

ls *.avi *.mkv *.mp4 *.vob >list_of_files

echo "Prompt before removing the source[Y/n]?"
read ch
while IFS="" read -r if || [[ -n "${if}" ]]; do
  if [[ "${ch}" == 'y' ]]; then
    get_ddecreased  && trimmer && rm_source
  elif [[ "${ch}" == 'n' ]]; then
    get_ddecreased && trimmer && rm "${if}"
  fi
  echo $if
done <list_of_files

echo -e "Removing list_of_files."
rm list_of_files 

如果用户y在询问时选择Prompt before removing the source[Y/n]trimmer完成修剪,则第一个文件rm_source旨在提示用户并等待他们的输入在删除源文件之前。这不起作用,因为脚本不会等待输入并立即继续,echo -e "Removing list_of_files."就像根本没有 while 循环一样。当用户n在询问时选择时, while 循环也不会执行Prompt before removing the source[Y/n]- 脚本直接继续,echo -e "Removing list_of_files."而不是迭代list_of_files.为什么这样?然而当我注释掉所有这些行时

if [[ "${ch}" == 'y' ]]; then
    get_ddecreased  && trimmer && rm_source
  elif [[ "${ch}" == 'n' ]]; then
    get_ddecreased && trimmer && rm "${if}"
  fi

在 while 循环内,所有行都list_of_files打印到屏幕上。

我的代码有什么问题吗?

答案1

您的代码本质上是执行以下操作:

foo () {
    read variable
}

while read something; do
    foo
done <input-file

目的是让readinfoo从终端读取某些内容,但是,它是在标准输入流从某个文件重定向的上下文中调用的。

这意味着readinfoo将从输入文件的输入流中读取,而不是从终端读取。

您可以通过使循环从标准输入之外的另一个文件描述符读取来规避此问题:

foo () {
    read variable
}

while read something <&3; do
    foo
done 3<input-file

这里,read循环中从文件描述符 3 读取,该描述符在关键字之后连接到输入文件done。这使得函数read中的foo可以自由地使用原始标准输入流。

bashshell 中,您可以让 shell 在 shell 变量中分配描述符,而不是对额外的文件描述符使用硬编码值:

foo () {
    read variable
}

while read something <&"$fd"; do
    foo
done {fd}<input-file

这可能会设置$fd为 10 或更大的整数。确切的值并不重要。


在问题中当前的代码中,您还可以通过避免创建和读取文件列表来解决问题,而是直接使用文件 glob:

for filename in *.avi *.mkv *.mp4 *.vob; do
    if [ ! -e "$filename" ]; then
        # skip non-existing names
        continue
    fi

    # use "$filename" here
    # ... and call your rm_source function
done

这完全避免了重定向。这还允许您的代码处理名称中带有换行符的奇怪文件。

循环中测试指定文件是否存在的语句if是必要的,因为默认情况下,如果该模式没有匹配的名称,shell 将保留通配模式。您可以通过设置来删除shellif中的语句bashnullglob shell 选项使用 shopt -s nullglob。设置此选项将使bashshell 完全删除不匹配的 glob。

另请注意,如果与通配模式匹配的任何名称是一个,则这与您的代码中的不同目录。如果您有一个名为 eg 的目录mydir.mp3,那么ls会列出内容该目录的。此外,如果与模式匹配的文件名以破折号开头,则使用的代码ls可能会将该名称误认为是一组选项。

相关内容