我有一个由以下组成的目录结构:
iTunes/Music/${author}/${album}/${song.mp3}
我实现了一个脚本,使用 lame(每次对一个文件进行操作)将我的 mp3 比特率降低到 128 kbps。
我的“normalize_mp3.sh”如下所示:
#!/bin/bash
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in *.mp3
do
lame --cbr $f __out.mp3
mv __out.mp3 $f
done
IFS=$SAVEIFS
如果我逐个文件夹并执行此命令,这可以正常工作,但我想要一个“全局”命令,就像在 4DOS 中一样,这样我就可以运行:
$ cd iTunes/Music
$ global normalize_mp3.sh
该global
命令将遍历所有子目录并执行normalize_mp3.sh
以删除所有子文件夹中的所有 mp3。
有人知道是否有与 4DOSglobal
命令等效的 Unix 命令吗?我尝试过,find -exec
但就是头疼。
答案1
这是所述global
命令的一个非常基本的实现bash
:
global() {
shopt -s globstar
origdir="$PWD"
for i in **/; do
cd "$i"
echo -n "${PWD}: "
eval "$@"
echo
cd "$origdir"
done
}
shopt -s globstar
启用递归通配符(**
我认为你需要 bash 版本 >= 4)**/
查找当前工作目录下的所有目录,因此$i
循环遍历这些目录- 我添加了一个
echo
,以便您可以看到循环当前位于哪个目录中 eval "$@"
计算函数的所有参数cd $origdir
返回到原始目录。(起初,我使用pushd
/ 的方法更优雅popd
,但我认为 cd back to$origdir
更安全,以防命令"$@"
本身更改目录(这当然是个坏主意!)
此global
命令接受带有参数的命令,例如global touch foo
。如果要使用更高级的 shell 语法(例如重定向或变量),则必须将命令括在单引号中'...'
。但是,如果您希望在每个目录中运行脚本,normalize_mp3.sh
则用法很简单
global normalize_mp3.sh
请注意,如果子目录很多,则第一个命令的启动可能需要一些时间来收集所有子目录。
当然,请使用最新的文件备份进行测试!
它的工作原理如下(我只能在 Linux 上测试,但我假设 bash 在 OSX 上以相同的方式运行):
$ echo $BASH_VERSION
4.1.5(1)-release
$ cd /var
$ tree -d | head
.
├── backups
├── cache
│ ├── apt
│ │ ├── apt-file
│ │ └── archives
│ │ └── partial
│ ├── cups
│ │ └── rss
│ ├── debconf
$ global 'echo -n This command is executed in $PWD at $(date); sleep 1'
/var/backups: This command is executed in /var/backups at Mo 1. Jul 12:11:00 CEST 2013
/var/cache: This command is executed in /var/cache at Mo 1. Jul 12:11:01 CEST 2013
/var/cache/apt: This command is executed in /var/cache/apt at Mo 1. Jul 12:11:02 CEST 2013
/var/cache/apt/apt-file: This command is executed in /var/cache/apt/apt-file at Mo 1. Jul 12:11:03 CEST 2013
/var/cache/apt/archives: This command is executed in /var/cache/apt/archives at Mo 1. Jul 12:11:04 CEST 2013
/var/cache/apt/archives/partial: This command is executed in /var/cache/apt/archives/partial at Mo 1. Jul 12:11:05 CEST 2013
/var/cache/cups: This command is executed in /var/cache/cups at Mo 1. Jul 12:11:07 CEST 2013
/var/cache/cups/rss: This command is executed in /var/cache/cups/rss at Mo 1. Jul 12:11:08 CEST 2013
/var/cache/debconf: This command is executed in /var/cache/debconf at Mo 1. Jul 12:11:09 CEST 2013
...
答案2
其他选择:
find ~/Music -name \*.mp3 | while IFS= read -r f; do
lame --cbr "$f" "$f"_temp
mv "$f"_temp "$f"
done
mdfind 'kMDItemAudioBitRate>128000' -onlyin ~/Music |\
parallel lame --cbr {} {}_temp \; mv {}_temp {}
f() { lame --cbr "$1" "$1"_temp; mv "$1"_temp "$1"; }
export -f f
mdfind 'kMDItemAudioBitRate>128000' -onlyin ~/Music | parallel f
shopt -s globstar # bash 4.0 or later
for f in ~/Music/**/*.mp3; do lame --cbr "$f" "$f"_temp; mv "$f"_temp "$f"; done
答案3
尝试以下方法:
#!/bin/bash
# first cd to iTunes/Music
for f in */*/*.mp3
do
lame --cbr "$f" __out.mp3 && mv __out.mp3 "$f"
done
无需设置IFS
。但您需要加上双引号$f
。我不知道如果出现问题,lame 是否有返回代码。也许先尝试使用printf "%s\n" "$f" first
而不是 lame 命令。
答案4
首先,编写一个稍微精简一点的脚本,循环遍历提供给脚本的参数。
#! /usr/bin/env bash
for f do
lame --cbr "$f" "$f_out.mp3"
mv "$f_out.mp3" "$f"
done
为了能够从任何地方访问它,请将脚本放在 $PATH 中的某个位置。在 Linux 上,我会使用$HOME/bin
,默认情况下它会添加到 $PATH 中;我怀疑 OSX 也是如此。
在单个 mp3 目录中使用它:
downrate_mp3 ./*.mp3
我更喜欢在这些 glob 前面加上./
,因为以 开头的文件名是合法的-
,但这可能会导致问题,因为许多程序(包括mv
)都希望以 开头的参数-
是一个选项,而不是一个文件。对于递归性,最好的方法是使用 globstar(需要 bash 4+,但我认为最近的 OSX 有这个):
shopt -s globstar
downrate_mp3 ./**/*.mp3
或者,您可以使用 find -exec
,正如您已经尝试过的那样:
find iTunes/Music -type f -name '*.mp3' -exec downrate_mp3 {} +
这对您现有的命令不起作用的原因是,它find
本质上试图使用大文件列表作为脚本的参数——而由于您问题中的脚本没有对参数执行任何操作,所以这显然不起作用。