【问题标题】:Loop recursively with two variables for a specific extension with Bash使用 Bash 使用两个变量递归循环特定扩展
【发布时间】:2017-02-12 01:25:06
【问题描述】:

我正在尝试使用 AtomicParsley 将专辑封面添加到 m4a 文件。以下适用于给定文件夹(有多个 m4a 文件,但只有一个 cover.jpg):

for f in *.m4a; do AtomicParsley "$f" --artwork cover.jpg --overWrite; done

由于我想递归地执行此操作(每个文件夹都有一个唯一的 cover.jpg 和多个 m4a 文件)并且所有子文件夹都具有相同的图像名称(即 cover.jpg 尽管它们不同),所以我尝试了以下但它会从第一个子文件夹中选择 cover.jpg 并应用于所有其他子文件夹(这是错误的,因为每个子文件夹都有自己的 cover.jpg)

for f in **/*.m4a; do
  for j in **/cover.jpg; do
    AtomicParsley "$f" --artwork $j --overWrite
  done
done

【问题讨论】:

标签: bash loops for-loop


【解决方案1】:

这更容易理解为找到每个包含cover.jpg 的目录,然后查找该目录中的所有视频文件。

shopt -s globstar
shopt -s nullglob
for c in **/cover.jpg; do
    d=${c%/cover.jpg}
    for f in "$d"/*.m4a; do
        AtomicParsley "$f" --artwork "$c" --overWrite
    done
done

【讨论】:

  • 不应该进行检查以防 glob 失败吗?
  • shopt -s nullglob?
  • 是的;对于第一个 glob,我并不担心(否则,为什么命令甚至正在运行?),但对于第二个 glob,这可能是一个合法的问题。
  • 我认为这不会捕获当前目录中的文件。取决于结构,可能不是问题,但值得注意。
  • @Fred - ** 确实适用于当前目录。
【解决方案2】:

试试这个:

for file in **/*.m4a; do
    AtomicParsley "$file" --artwork "$(dirname "$file")/cover.jpg" --overWrite
done

【讨论】:

  • 您可能需要在循环中添加检查,以防 glob 失败。
  • 我相信我面临多个问题 1) 文件夹/文件名中的空间不起作用 2) --artwork "$(/Volumes/music/$file)/cover.jpg看起来,它正在将文件名($file)添加到不起作用的文件路径(我可能在这里错了)
  • 您是对的,因为 (1) 不支持空格。我已经更新了,请检查。
  • 对于 (2) 不要用您的目录名称替换 dirname,它应该按原样读取 dirname - 那是命令的名称。例如dirname /foo/bar 返回/foo
【解决方案3】:

试试这个:

main_dir="/path/to/dir"
while IFS= read -r -d '' m4a_file
do
  AtomicParsley "$m4a_file" --artwork "${m4a_file%/*}/cover.jpg" --overWrite
done < <(find "$main_dir" -type f -name "*.m4a" -print0)

这也是可能的:

shopt -s globstar
shopt -s nullglob
main_dir="/path/to/dir"
cd "$main_dir"
for m4a_file in **/*.m4a
do
  AtomicParsley "$m4a_file" --artwork "${m4a_file%/*}/cover.jpg" --overWrite
done

globstar shell 选项启用 ** 递归通配(即进入子目录、子目录的子目录等)。

默认行为或 globbing 是如果没有匹配,则 glob 模式扩展到自身。当这种情况发生在for 循环中时,您必须处理文件不存在的情况,因为失败的 glob 将导致循环体执行一次,变量中的文件名无效。 nullglob 选项更改了默认行为,因此失败的 glob 将扩展为空。在for 循环的情况下,循环根本不会执行。

【讨论】:

  • @chepner 我已经习惯了需要对我找到的文件进行各种处理(我实际上并不经常使用-exec)。我需要确定如何进行扩展以提取目录路径。我猜我是按照我平时做事的方式去做的。
  • 三思而后行,因为封面文件名需要处理;你必须做类似-exec sh -c '...' _ {} 这样的事情,这会变得很乱。
  • 第一个解决方案在意外标记“
  • 第二种方法效果很好,是的根目录没有m4a文件
【解决方案4】:

对于允许您添加更多功能并使用相同例程进行不同处理的更复杂的解决方案(有点矫枉过正),您可以执行以下操作:

parsley_file()
{
    AtomicParsley "$1" --artwork cover.jpg --overWrite
}
doforeach_arg()
{
    local callback="$1"
    shift
    local arg
    for arg in "${@}"; do "${callback}" "${arg}"; done
}
parsley_dir()
( # use '(',')' for subshell so shopt only affects this function
    local dir="${1}"
    shopt -s nullglob
    doforeach_arg parsley_file "${dir}"/*.m4a
)

find_dirs()
{
  find "${1}" -type d | sort
}
doforeach_line()
{
  local callback="${1}"
  local line
  while read line; do "${callback}" "${line}"; done
}
parsley_dirs()
{
    find_dirs "${1}" | doforeach_line parsley_dir
}

parsley_dirs "."

否则,如果您想要的话,对于几乎单线的东西来说更简单:

shopt -s nullglob
find "$PWD" - type d | while read dir ; do cd "$dir" && [[ -f cover.jpg ]] && ( for f in *.m4a ; do AtomicParsley "$f" -cover cover.jpg ; done ) ; done

【讨论】:

  • 据我所知,'**' 是一种常见的 java / ant 文件匹配语法,在 bash 中不起作用,除非它是最近的扩展。 (它仍然只会匹配一个目录级别,不会递归)
  • ** 是在 Bash 4.0 中引入的,必须使用 shopt -s globstar 启用。
  • 很高兴认识本杰明 W!会让很多事情变得更容易
猜你喜欢
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-15
  • 2017-06-15
  • 2020-10-20
  • 1970-01-01
  • 2023-03-30
相关资源
最近更新 更多