find
と xargs
の組み合わせで空白文字が入った行があるとハマる場合があります。
$ find /Library/Desktop\ Pictures -name Yosemite\*
/Library/Desktop Pictures/.thumbnails/Yosemite 2.jpg
/Library/Desktop Pictures/.thumbnails/Yosemite 3.jpg
/Library/Desktop Pictures/.thumbnails/Yosemite 4.jpg
/Library/Desktop Pictures/.thumbnails/Yosemite 5.jpg
/Library/Desktop Pictures/.thumbnails/Yosemite.jpg
/Library/Desktop Pictures/Yosemite 2.jpg
/Library/Desktop Pictures/Yosemite 3.jpg
/Library/Desktop Pictures/Yosemite 4.jpg
/Library/Desktop Pictures/Yosemite 5.jpg
/Library/Desktop Pictures/Yosemite.jpg
サムネイルディレクトリのパスは grep -F -v /.thumbnails
で除きつつ
$ find /Library/Desktop\ Pictures -name Yosemite\* | grep -F -v /.thumbnails
/Library/Desktop Pictures/Yosemite 2.jpg
/Library/Desktop Pictures/Yosemite 3.jpg
/Library/Desktop Pictures/Yosemite 4.jpg
/Library/Desktop Pictures/Yosemite 5.jpg
/Library/Desktop Pictures/Yosemite.jpg
(grep
の -F
は「正規表現を使わない」、-v
は文字列にマッチしたものを表示しないという意味です)
この5つのファイルについて ls -l
の結果を見たい場合
$ find /Library/Desktop\ Pictures -name Yosemite\* | grep -F -v /.thumbnails | xargs ls -l
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: 2.jpg: No such file or directory
ls: 3.jpg: No such file or directory
ls: 4.jpg: No such file or directory
ls: 5.jpg: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite.jpg: No such file or directory
といった感じでエラーになってしまいます。
これは find /Library/Desktop\ Pictures -name Yosemite\* | grep -F -v /.thumbnails
の結果を xargs
によって空白類文字(改行文字や空白文字)で区切られた引数の列として ls -l
に与えているからです(言葉にしようとすると少しややこしい…)
もっと結果が小さいとわかりやすい?
$ touch "toei bus"
$ touch "tokyo bus"
$ ls -l
total 0
-rw-r--r-- 1 ogata staff 0 7 3 21:30 toei bus
-rw-r--r-- 1 ogata staff 0 7 3 21:30 tokyo bus
$ find . -type f
./toei bus
./tokyo bus
$ find . -type f | xargs ls -l
ls: ./toei: No such file or directory
ls: ./tokyo: No such file or directory
ls: bus: No such file or directory
ls: bus: No such file or directory
2行が2引数になると思ったら、空白も引数の区切り文字になって4引数として解釈されてしまったという感じ。
これの解決には find
の -print0
オプション、そして xargs
の -0
オプションを使います。
$ find . -type f -print0 | xargs -0 ls -l
-rw-r--r-- 1 ogata staff 0 7 3 21:30 ./toei bus
-rw-r--r-- 1 ogata staff 0 7 3 21:30 ./tokyo bus
find
は -print0
によって、行ごとの出力を改行区切りではなくヌルバイト文字区切りとして出力し、xargs
は入力を空白類文字ではなくヌルバイト文字で分割したそれぞれの文字列を指定のコマンド(この場合は ls -l
)の引数の列とします。
Mac の Spotlight のコマンドラインインターフェースである mdfind
にも -0
というオプションがあります(-print0
ではないことに注意)。
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'"
/Library/Desktop Pictures/Yosemite.jpg
/Library/Desktop Pictures/Yosemite 5.jpg
/Library/Desktop Pictures/Yosemite 4.jpg
/Library/Desktop Pictures/Yosemite 3.jpg
/Library/Desktop Pictures/Yosemite 2.jpg
この出力を ls -l
するとき
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | xargs ls -l
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: /Library/Desktop: No such file or directory
ls: 2.jpg: No such file or directory
ls: 3.jpg: No such file or directory
ls: 4.jpg: No such file or directory
ls: 5.jpg: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite: No such file or directory
ls: Pictures/Yosemite.jpg: No such file or directory
と、先ほどの find
と同様にうまくいかないものを
$ mdfind -0 "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | xargs -0 ls -l
-rw-r--r-- 1 root wheel 9993401 8 26 2015 /Library/Desktop Pictures/Yosemite 2.jpg
-rw-r--r-- 1 root wheel 12361649 8 26 2015 /Library/Desktop Pictures/Yosemite 3.jpg
-rw-r--r-- 1 root wheel 10539824 8 26 2015 /Library/Desktop Pictures/Yosemite 4.jpg
-rw-r--r-- 1 root wheel 4617954 8 26 2015 /Library/Desktop Pictures/Yosemite 5.jpg
-rw-r--r-- 1 root wheel 11914786 8 26 2015 /Library/Desktop Pictures/Yosemite.jpg
とすることでうまく渡すことができます
本題とは関係ありませんが、mdfind
コマンドのクエリ文字列のキーは mdls
コマンドで類推することができます。
$ mdls /Library/Desktop\ Pictures/Yosemite.jpg
_kMDItemOwnerUserID = 0
kMDItemContentCreationDate = 2015-08-26 00:13:34 +0000
kMDItemContentModificationDate = 2015-08-26 00:13:34 +0000
kMDItemContentType = "public.jpeg"
kMDItemContentTypeTree = (
"public.jpeg",
"public.image",
"public.data",
"public.item",
"public.content"
)
kMDItemDateAdded = 2016-03-09 16:42:27 +0000
kMDItemDisplayName = "Yosemite.jpg"
kMDItemFSContentChangeDate = 2015-08-26 00:13:34 +0000
kMDItemFSCreationDate = 2015-08-26 00:13:34 +0000
kMDItemFSCreatorCode = ""
kMDItemFSFinderFlags = 0
kMDItemFSHasCustomIcon = (null)
kMDItemFSInvisible = 0
kMDItemFSIsExtensionHidden = 0
kMDItemFSIsStationery = (null)
kMDItemFSLabel = 0
kMDItemFSName = "Yosemite.jpg"
kMDItemFSNodeCount = (null)
kMDItemFSOwnerGroupID = 0
kMDItemFSOwnerUserID = 0
kMDItemFSSize = 11914786
kMDItemFSTypeCode = ""
kMDItemKind = "JPEG イメージ"
kMDItemLogicalSize = 11914786
kMDItemPhysicalSize = 11915264
kMDItemSupportFileType = (
MDSystemFile
)
画像を find
もしくは mdfind
で選別して、それを peco に渡して1つ選択してそれを Mac のデスクトップの壁紙にすることを考えてみます。
壁紙の変更は私が以前書いた「Mac の JXA で現在のデスクトップの壁紙を変更する - Qiita」で解説した macgui wallpaper
コマンドを使います。今回も、ファイルパスに空白文字列が含まれている場合を想定することにします。
find
または mdfind
と peco
コマンドの間は、全て行指向の絞り込みなので -print0
や -0
の必要はありません。
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | peco
/Library/Desktop Pictures/Yosemite.jpg
(上記結果は peco の絞り込みインターフェースで当該ファイルを選んだ場合)
ただ、この peco の絞り込み結果を xargs
に渡すときには空白類文字を考慮する必要があります。
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | peco | xargs macgui wallpaper
"/Library/Desktop" is not found
今回想定しているような peco
で絞り込まれる結果が1行のみの場合は(壁紙画像指定は唯一つの画像ファイル)、xargs
に -0
オプションを与えることで「空白類文字は区切りではなく、それも通常の文字の一つとしてまとめて macgui wallpaper
に与える」ということになり、うまくいくはずです。
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | peco | xargs -0 macgui wallpaper
"/Library/Desktop Pictures/Yosemite.jpg
" is not found
あれ、うまくいかない……。
よく見ると「ファイルがない」と言われていますが、どうもファイル名の末尾に改行文字が付いたもの全体をファイル名として渡しているようです。
改行文字を落とすのであれば perl -ne 'chomp if eof; print;'
というコマンドを peco
の後ろのフィルタに入れることで、改行を落とすことができます。
$ mdfind "kMDItemContentTypeTree=public.image && kMDItemFSName='*Yosemite*'" | peco | perl -ne 'chomp if eof; print;' | xargs -0 macgui wallpaper
true
うまくいきました。
この末尾改行落としワンライナーをよく使うなら、スクリプトとして実行権限を付けてパスの通ったところに置いておくとよいでしょう。
#!/usr/bin/perl
use strict;
use warnings;
while (<>) {
chomp if eof;
print;
}
または
#!/usr/bin/perl -n
chomp if eof;
print;
peco --help
の結果を見ると、peco
には --null
オプションがありますが、これが生きてくるのは peco の選択モードで Ctrl+Space を使い複数行を選択した場合です。--null
オプションがある場合、peco
は選択された複数行を改行区切りではなくヌルバイト文字区切りで出力します。パイプでつながれた場合には右側のコマンドがヌルバイト文字区切りの入力を受け取ります。
壁紙変更の例では一行のみの選択だったので、--null
オプションの出番はありませんでしたが、peco
で複数行選択する場合には --null
オプションが活躍します。