すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

【find】複数ファイルをまとめてafconvertする【xargs】

こんばんは.1000chです. 翌日午前中から予定があると,夜中に無理出来なくて進捗がアレですね. ライフサイクルをずらせ,という話でしょうか.

さて,今回はunixコマンドネタです.

いきますよー.

問題

今回ぶつかった問題はこんな感じです.

「ディレクトリ内のxxx.mp3を全て,xxx.wavに変換したい!」

iTunesでもなんでもつかえばいいって?そーゆーことじゃねーんだよ

実はMacには,ターミナル上で音声データを変換するコマンドが最初から入っています. それがafconvertというコマンドです. このコマンド,wavやらmp3やらaiffやらの音声ファイルを双方向に変換できる便利ツールなんです. (が,オプションの指定が結構複雑で,細かいこと考えると使い方難しいかも)

細かい説明は他にゆずって,mp3->wavの場合はこんな感じでコマンド叩きます.

$ afconvert -f WAVE -d LEI16 <input>.mp3 (<output>.wav)
#output名はoptional

具体的にはこんな感じ.

1000ch$ ls
1-03 Snow halation.mp3

1000ch$ afconvert -f WAVE -d LEI16@44100 1-03\ Snow\ halation.mp3 snow.wav

1000ch$ ls -1
1-03 Snow halation.mp3
snow.wav

スノハレ,良い曲ですね.

afconvert,1ファイルだけなら楽なんですが,複数ファイルが対象になるとちょっと面倒です.

$ ls -1
12 START_ DASH!! (ELI Mix).mp3
12 START_ DASH!! (NOZOMI Mix).mp3
13 START_ DASH!! (MAKI Mix).mp3
14 START_ DASH!! (HONOKA Mix).mp3
14 START_ DASH!! (KOTORI Mix).mp3
14 START_ DASH!! (UMI Mix).mp3
15 START_ DASH!! (HANAYO Mix).mp3
15 START_ DASH!! (NICO Mix).mp3
15 START_ DASH!! (RIN Mix).mp3

# まとめて処理したいなー
$ afconvert -f WAVE -d LEI16  *.mp3

    Audio File Convert
    Version: 2.0
    Copyright 2003-2013, Apple Inc. All Rights Reserved.
    Specify -h (-help) for command options

Usage:
afconvert [option...] input_file [output_file]
    Options may appear before or after the direct arguments. If output_file
    is not specified, a name is generated programmatically and the file
    is written into the same directory as input_file.
afconvert input_file [-o output_file [option...]]...
    Output file options apply to the previous output_file. Other options
    may appear anywhere.
Help options:
    { -hf | --help-formats }
        print a list of supported file/data formats
    { -h | --help }
        print help

# 使い方が間違っているので利用方法のメッセージが出てきた
# え,じゃー9回分afconvert打つの?だる..

スタダも良い曲ですね.

こーゆーのはafconvertに限った話ではなく,複数ファイルに同一の処理を行いたいことってよくありますよね. シェルスクリプト書けばできるけど,そこまでする?みたいなやつ.

てことで,「複数ファイルに処理を行う」方法を調べました.

find + xargs

ちょいとggって出てきたのがこちら.

find/xargsを使ったファイル・ディレクトリ名の一括置換/一括作成コマンド一覧

どうやらfindコマンドとxargsコマンドを組み合わせると,逐次処理っぽいのができるらしい.

xargsを初めて知ったので使い方を調べます.

Linuxコマンド集 - 【xargs】標準入力から生成したコマンドラインを実行する:ITpro

要するに,

標準出力を受けてコマンドを生成,実行するコマンドのようです. てきとーに出力をパイプで与えてあげればいいみたい.

試しにやってみます.

1000ch$ ls
bar.txt  hoge.txt huga.txt log      piyo.csv tmp

# lsした結果をxargsに渡す.デフォルトではechoが実行される
1000ch$ ls | xargs
bar.txt hoge.txt huga.txt log piyo.csv tmp

# これと同値
1000ch$ ls | xargs echo
bar.txt hoge.txt huga.txt log piyo.csv tmp

# ls -aした結果を(ry
1000ch$ ls -a | xargs
. .. bar.txt hoge.txt huga.txt log piyo.csv tmp

# ls-lした(ry
1000ch$ ls -l | xargs
total 8 -rw-r--r-- 1 1000ch staff 0 11 11 11:29 bar.txt -rw-r--r-- 1 1000ch staff 0 11 11 11:29 hoge.txt -rw-r--r-- 1 1000ch staff 0 11 11 11:29 huga.txt -rw-r--r-- 1 1000ch staff 21 11 11 11:52 log -rw-r--r-- 1 1000ch staff 0 11 11 11:29 piyo.csv drwxr-xr-x 5 1000ch staff 170 11 11 12:52 tmp

基本的に,パイプして与えた文字列をひとかたまりで処理してる感じですね.

xargsで実行するコマンドに,明示的に引数を与える場合は-Iを使うみたい.(Macの場合)

# -Iオプションで置換文字列を指定
1000ch$ ls | xargs -I{} echo {}
bar.txt
hoge.txt
huga.txt
log
piyo.csv
tmp

# コレはダメ
1000ch$ ls | xargs -I echo {}
xargs: {}: No such file or directory

# コレもダメ
1000ch$ ls | xargs echo {}
{} bar.txt hoge.txt huga.txt log piyo.csv tmp

# 置換文字列は自由に指定可能
1000ch$ ls | xargs -I% echo %
bar.txt
hoge.txt
huga.txt
log
piyo.csv
tmp

これだけで先のafconvertは上手く出来そうな感じがします. 左側をlsから,find . -name '*.mp3'とかにすれば良さそう.

1000ch$ ls -1
12 START_ DASH!! (ELI Mix).mp3
12 START_ DASH!! (NOZOMI Mix).mp3
13 START_ DASH!! (MAKI Mix).mp3
14 START_ DASH!! (HONOKA Mix).mp3
14 START_ DASH!! (KOTORI Mix).mp3
14 START_ DASH!! (UMI Mix).mp3
15 START_ DASH!! (HANAYO Mix).mp3
15 START_ DASH!! (NICO Mix).mp3
15 START_ DASH!! (RIN Mix).mp3

# カレントディレクトリ内のmp3ファイルをwaveに変換
1000ch$ find . -name "*.mp3" | xargs -I % afconvert -f WAVE -d LEI16@44100 %

1000ch$ ls -1
12 START_ DASH!! (ELI Mix).mp3
12 START_ DASH!! (ELI Mix).wav
12 START_ DASH!! (NOZOMI Mix).mp3
12 START_ DASH!! (NOZOMI Mix).wav
13 START_ DASH!! (MAKI Mix).mp3
13 START_ DASH!! (MAKI Mix).wav
14 START_ DASH!! (HONOKA Mix).mp3
14 START_ DASH!! (HONOKA Mix).wav
14 START_ DASH!! (KOTORI Mix).mp3
14 START_ DASH!! (KOTORI Mix).wav
14 START_ DASH!! (UMI Mix).mp3
14 START_ DASH!! (UMI Mix).wav
15 START_ DASH!! (HANAYO Mix).mp3
15 START_ DASH!! (HANAYO Mix).wav
15 START_ DASH!! (NICO Mix).mp3
15 START_ DASH!! (NICO Mix).wav
15 START_ DASH!! (RIN Mix).mp3
15 START_ DASH!! (RIN Mix).wav

やったぜ(`・ω・´)

なお,xargsではデフォルトで空白を区切り文字と見なしているようで,このままだと空白を含むファイルを扱う際に思うような挙動にならないことがあります. この問題を解決するには,区切り文字を明示的に変更する必要があります. やることは簡単で,find側では-print0オプションを,xargs側では-0オプションをつけるだけ.

(..らしいんだけど,手元だとなぜか空白はokで,アポストロフィがNGだった.Mac版の仕様なのでしょうか)

1000ch$ tree
.
├── hello\ world
│   └── hello\ world.txt
├── my\ name\ is\ 1000ch.txt
└── what's\ your\ name
    └── 1000ch.txt

2 directories, 3 files

1000ch$ find . -name "*.txt" | xargs -I % echo %
./hello world/hello world.txt # 空白含んでいても出力ok
./my name is 1000ch.txt
xargs: unterminated quote #クオートを含む名前がNG

1000ch$ find . -name "*.txt" -print0| xargs -0 -I % echo %
./hello world/hello world.txt
./my name is 1000ch.txt
./what's your name/1000ch.txt #クオート含む名前も出力ok

更に,xargsへの標準出力は間にsed噛ませてちょこっと変更させたりも出来ます. 応用すると全ての.txtファイルを.mdに変換なんてこともできたり.

1000ch$ ls -1
ts1.txt
ts2.txt
ts3.txt

# 拡張子.txtのファイルを探して
# .txtを外した文字列strを取得して
# str.txtをstr.mdにcp
1000ch$ find . -name "*.txt" | sed -e "s/.txt//" | xargs -I % cp %.txt %.md

1000ch$ ls -1
ts1.md
ts1.txt
ts2.md
ts2.txt
ts3.md
ts3.txt

find + sed + xargs の使い方をおぼえておくと,いざという時に役に立つかも?

後日談

実はfindには-execっていうオプションがあって,ヒットしたファイルに逐次コマンドを実行することが出来るらしい.

というわけで,単にディレクトリ内の全ファイルをafconvertするだけならこれでok.

$ find . -name "*.mp3" -exec afconvert -f WAVE -d LEI16 {} \;

xargsとか使う必要なかったですね.

ちなみに,find -execxargsの違いは,xargsにおいてはfindの結果を一つの固まりとして扱うことが出来ることだそうです. ふむ...どんな利点があるのやら.

まぁ,xargs使った方がコマンド何したいのか,はわかりやすいんじゃないでしょうか. どうせワンライナーなんで,好きなようにやれば良いと思います←

まとめ

  • afconvert を一気にやりたい
  • find + xargs 便利
  • find + sed + xargs も便利
  • find -execで事足りるっちゃ事足りる