シェルスクリプトで名前にスペース(空白)を含むファイルがxargsでうまく処理できない場合の対処
はじめに
チームメンバから「名前にスペースを含むファイルをxargsでうまく取り扱えない」という相談を受けました。
調べたらすぐに解決する あれ だと思ったのですが、 find
ではなく grep
の実行結果を xargs
に渡していたので、なるほどあまり見ないケースだなと思ったついでにエントリをまとめました。
検証環境
$ uname -moi
x86_64 MacBookPro16,1 Darwin
$ bash -version | head -n 1
GNU bash, バージョン 5.0.18(1)-release (x86_64-apple-darwin19.5.0)
$ grep --version
grep (BSD grep) 2.5.1-FreeBSD
$ find --version
find (GNU findutils) 4.7.0
"名前にスペース(空白)を含むファイルがxargsでうまく処理できない" とはどんな問題?
シェルあるあるではありますが 、実際にどんな問題なのか見ていきます。
まずは、ファイルを3つ作成します。それぞれ以下の名前とします。
hello1.txt
hello 2.txt
hello 3.txt
$ touch "hello1.txt"
$ touch "hello 2.txt"
$ touch "hello 3.txt"
ここで find
コマンドを使ってファイルの一覧を xargs
に渡して echo
を実行させてみます。
-n 1
は標準入力の内容を1つずつ処理するオプションです。
$ find . -type f | xargs -n 1 echo
./hello
3.txt
./hello
2.txt
./hello1.txt
悲しいことに hello 2.txt
、 hello 3.txt
というファイル名が分割して出力されました。 xargs
は標準入力からもらった内容を 改行 や 空白 で区切って処理するためです。
説明するだけで長くなりましたね。 これを解決するための方法を紹介します。
対処法 : find
コマンドの実行結果をxargsで処理する場合
find
コマンドには -print0
というオプションがあります。
オプションなしの場合は以下のように改行区切りでファイル名が出力されます。
$ find . -type f
./hello 3.txt
./hello 2.txt
./hello1.txt
ここで -print0
オプションを付与すると以下のように出力内容が変わります。
$ find . -type f -print0
./hello 3.txt./hello 2.txt./hello1.txt%
ターミナル上はわからないのですが、区切り文字が改行から null文字 ( \0
) と言われる特殊な文字に変わります。
この状態で xargs
に渡してみましょう。
$ find . -type f -print0 | xargs -n 1 echo
xargs: WARNING: a NUL character occurred in the input. It cannot be passed through in the argument list. Did you mean to use the --null option?
./hello
3.txt
2.txt
少しだけ出力内容が変わりました。 「xargs
コマンド実行しているけど、 --null
文字つけたかったんじゃないの?」と。
xargs
に --null
オプションを付与すると、デフォルトの区切り文字を 改行 や 空白 ではなく null文字 ( \0
) を使うようになります。
$ find . -type f -print0 | xargs --null -n 1 echo
./hello 3.txt
./hello 2.txt
./hello1.txt
ちなみに、 -0
オプションも同じ意味になります。
$ find . -type f -print0 | xargs --null -n 1 echo
./hello 3.txt
./hello 2.txt
./hello1.txt
ここまではよくあるケースですね。
対処法 : grep -l
コマンドの実行結果を xargs
で処理する場合
grep -l
というコマンドで、 grep
で検索して該当したファイルの名前だけを出力させることができます。
先程作成した3つのファイルに適当なデータを格納しておきます。
$ seq 10 > "hello1.txt"
$ seq 20 > "hello 2.txt"
$ seq 11 20 > "hello 3.txt"
ファイルの中身は以下のようになります。
$ cat hello1.txt
1
2
3
4
5
6
7
8
9
10
$ cat hello\ 2.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ cat hello\ 3.txt
11
12
13
14
15
16
17
18
19
20
ここで、 10 という数値を含むファイルだけを検索するためには以下のコマンドを実行します。
$ grep -l 10 *
hello 2.txt
hello1.txt
この結果を xargs
に渡して echo
しようとすると、 find
と同じ問題に直面します。
$ grep -l 10 * | xargs -n 1 echo
hello
2.txt
hello1.txt
解決方法も find
と同じです。 --null
オプションというものが用意されており、これを利用します。
$ grep -l 10 * --null | xargs --null -n 1 echo
hello 2.txt
hello1.txt
tar
コマンドにも xargs
と同じようにnull文字を区切り文字として受け取るオプションがある
xargs
だけではなく、 tar
コマンドにも圧縮対象のファイルを標準入力から受け取る際に null文字 を区切り文字として利用するオプションがあります。
過去のエントリでもサラリと紹介していました
$ man tar | grep null -C 2
again with HFS+ compression.
--null (use with -I or -T) Filenames or patterns are separated by null
characters, not by newlines. This is often used to read file-
names output by the -print0 option to find(1).
--
--
a character or block device such as a tape drive. If the output is being
written to a regular file, the last block will not be padded. Many com-
pressors, including gzip(1) and bzip2(1), complain about the null padding
when decompressing an archive created by tar, although they still extract
it correctly.
ひとこと
grep
より高速と言われる ag
コマンドというものがありますが、やはり同じようなオプションが用意されています。
$ ag --help | grep 'null'
-0 --null --print0 Separate filenames with null (for 'xargs -0')
ファイル名を標準出力に出力するコマンド 、 ファイル名を標準入力から受け取るコマンド には、 大体区切り文字として null文字 を利用するためのオプションがあるのでしょう。
他にもコマンド、オプションを見つけた方はコメント下さい!
ディスカッション
コメント一覧
まだ、コメントがありません