【问题标题】:Error when extracting a filename to name other files提取文件名以命名其他文件时出错
【发布时间】:2017-07-16 16:55:27
【问题描述】:

我正在尝试使用 FFmpeg 将视频转换为 Bash 中的图像。我想使用视频的文件名来命名相应的图像(后跟一个整数)。

如果我将文件导出到同一目录中,我可以做到这一点:

for file in `find . -name "*.mp4"`; do ffmpeg -i $file -q 1 $file'_'%d.jpeg; done

但是,如果我要将图像导出到所需的目录,则会出现错误。看起来 $file 的值不仅仅是文件名,而是一个目录。

我在尝试什么:

for file in `find . -name "*.mp4"`; do ffmpeg -i $file -q 1 ~/testfolder/$file'_'%d.jpeg; done

我的问题是:我怎样才能正确提取要在此处使用的文件名?

【问题讨论】:

    标签: bash file-handling


    【解决方案1】:

    这样做,因为它适用于包含任何特殊字符的文件(包括换行符,很少使用但在文件名中有效)。

    destdir=~/testfolder/
    while read -u 5 -r -d '' file
    do
      name=$(basename "$file")
      ffmpeg -i "$file" -q 1 "$destdir/${file}_%d.jpeg" </dev/null
    done 5< <(find . -name "*.mp4" -print0)
    

    basename 命令是一个外部程序。您可以通过以下几行仅使用 shell 扩展来获得相同的结果:

    name=${file%/}
    name=${name##*/}
    

    【讨论】:

    • 请注意,您可能需要将ffmpeg 的标准输入从/dev/null 重定向,这样它就不会从find 命令的输出中读取。
    • 我为循环添加了一个文件描述符,并从/dev/null 重定向ffmpeg 输入。 ffmpep 是否实际上是从标准输入读取的,还是更多的一般预防措施?
    【解决方案2】:

    bash 命令basename 可以用于此。

    在这种情况下,下面应该只列出文件:

    for file in $(find . -name "*.mp4"); do echo $(basename $file); done
    

    您可以在原始代码中使用$(basename $file) 仅获取文件名。

    for file in $(find . -name "*.mp4"); do 
      ffmpeg -i "${file}" -q 1 "~/testfolder/$(basename $file)'_'%d.jpeg"
    done
    

    【讨论】:

    • 非常感谢@Danny!有用!为了澄清,我需要用 ~/testfolder/$(basename $file)''%d.jpeg 替换 ~/testfolder/$file''%d.jpeg
    • 啊是的 - 我错过了它被使用了两次。我现在已经更正了这个答案。如果可行 - 请您将其标记为已接受的答案吗?
    • 请注意,在for 循环中使用命令替换将中断任何包含导致分词操作的字符(例如空格)的文件。考虑使用while 循环,将find 命令馈送到循环中,并使用进程替换(&lt; &lt;(find...))。
    • 这是有道理的。我试图在 OP 代码的上下文中回答这个问题。感谢您的进一步阅读@mklement0,我现在就消化一下。
    • @DannyStaple:您需要@-提及我才能收到回复通知。管道涉及子外壳,如果您在子外壳中定义变量,则当前外壳看不到它们。使用进程替换允许您在当前 shell 中运行 read。比较 unset var; echo 'hi' | read var; declare -p varunset var; read var &lt; &lt;(echo 'hi'); declare -p var
    【解决方案3】:

    你既不需要 basename 也不需要循环; 您可以使用-type f 来避免find 在其结果中报告目录,并结合-exec 选项直接执行您的命令。

    这样的东西应该适用于您的所有 mp4 文件:

    find . -type f -name '*.mp4' -exec bash -c 'ffmpeg -i $0 -q 1 $0'_'%d.jpeg' {} \; 
    

    看一个小演示测试:

    $ find . -type f -name 'a*.txt' -exec bash -c 'echo ffmpeg -i $0 ' {} \; 
    ffmpeg -i ./a.txt
    ffmpeg -i ./a spaced file.txt
    ffmpeg -i ./aa.txt
    ffmpeg -i ./cheatsheets/awk-cheat-sheet-gv.txt
    

    如您所见,第二个文件的文件名中有空格,但由 find 正确处理。
    相反,这样的间隔文件会以for loop 中断。

    如果您坚持使用循环来完成这项工作,那么这必须是 Fred 建议的 while 循环。

    作为对 Fred 解决方案的修改,您可以使用 find-printf 功能避免使用 basename:

    while read -r -d '' file;do 
      ffmpeg -i "$file" -q 1 "${file}_%d.jpeg"
    done < <(find . -name "*.mp4" -printf %f\\0)
    

    -printf %f 根据find 的手册页打印剥离的文件名,并在每个文件名后附加\\0(空字符),即使名称包含空格或其他特殊字符,我们也可以确保正确处理文件名。

    小测试:

    $ while read -r -d '' file;do echo "ffmpeg -i $file -q 1 ${file}_%d.jpeg";done < <(find . -name "a*.txt" -printf %f\\0)
    ffmpeg -i a.txt -q 1 a.txt_%d.jpeg
    ffmpeg -i a spaced file.txt -q 1 a spaced file.txt_%d.jpeg
    ffmpeg -i aa.txt -q 1 aa.txt_%d.jpeg
    ffmpeg -i awk-cheat-sheet-gv.txt -q 1 awk-cheat-sheet-gv.txt_%d.jpeg
    

    【讨论】:

    • 嗯 - 此解决方案不会将输出文件放入目标 - 它会将它们直接写回输入文件夹 - 我认为这不是 OP 在这里要求的。我不认为它实际上回答了这个问题。
    • 我不认为它不起作用。 find 的 -exec 部分不是由我构建的,而是由 OP (ffmpeg -i $0 -q 1 $0'_'%d.jpeg) 构建的。我只是将它包含在 find 中,仅此而已。
    猜你喜欢
    • 2021-10-08
    • 2015-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-31
    • 2019-01-04
    • 1970-01-01
    • 2019-08-08
    相关资源
    最近更新 更多