3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

find, mdfind, xargs, peco で空白入りのパス文字列を扱う

Last updated at Posted at 2018-08-03

findxargs の組み合わせで空白文字が入った行があるとハマる場合があります。

$ 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 または mdfindpeco コマンドの間は、全て行指向の絞り込みなので -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

うまくいきました。

この末尾改行落としワンライナーをよく使うなら、スクリプトとして実行権限を付けてパスの通ったところに置いておくとよいでしょう。

chomp
#!/usr/bin/perl

use strict;
use warnings;

while (<>) {
    chomp if eof;
    print;
}

または

chomp
#!/usr/bin/perl -n
chomp if eof;
print;

peco --help の結果を見ると、peco には --null オプションがありますが、これが生きてくるのは peco の選択モードで Ctrl+Space を使い複数行を選択した場合です。--null オプションがある場合、peco は選択された複数行を改行区切りではなくヌルバイト文字区切りで出力します。パイプでつながれた場合には右側のコマンドがヌルバイト文字区切りの入力を受け取ります。

壁紙変更の例では一行のみの選択だったので、--null オプションの出番はありませんでしたが、peco で複数行選択する場合には --null オプションが活躍します。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?