1
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 3 years have passed since last update.

シェルで入出力のスペース区切りに悩まされたときの解決法

Posted at

本記事では、コマンド間で文字列をやり取りするときに勝手にスペースで区切られてしまうことで起きる問題の解決法をまとめます。

前提とする環境

  • Linux (Ubuntu)
  • bash

Linux 周りに疎いため、これが必要十分かどうかはわかりかねます。(さすがにLinux以外だとコマンドの挙動が変わってくると思われますが、Ubuntu は絞りすぎかも。bash についても同様で他の sh でも同じかも。)

問題提起

問題の例を1つ挙げます。たとえば、以下のようなケースです。

$ ls
'file ss.txt'   file1.txt
$
$ find . -type f | xargs -r ls -l
ls: cannot access './file': No such file or directory
ls: cannot access 'ss.txt': No such file or directory
-rw-r--r-- 1 user group    0 Feb 27 21:50  ./file1.txt

find . -type f でカレントディレクトリ以下のすべてのファイルを取得し、それらを ls -l に引数として渡そうとしていますが、ls でエラーが発生しています。もしうまくいけば、file1.txt だけでなく ls -l の引数として渡されたすべてのファイルについて更新日時や所有者などの情報が表示されるはずです。

原因

原因を端的に言えば、find の出力と xargs の入力の解釈に齟齬があるためです。find は複数のファイルパスを改行区切りで出力しているのに対し、xargs は入力をスペースと改行で区切っています1

これを明らかにするため、まず前半の find . -type f の出力を観察してみます。

$ find . -type f
./file ss.txt
./file1.txt

このように、find . -type f では改行区切りでファイルパスを出力しています。この標準出力が xargs コマンドによって引数として ls -l に与えられるので、気持ちとしては次のようになることを期待しています。

  • コマンド: ls
  • 第一引数: -l
  • 第二引数: ./file ss.txt
  • 第三引数: ./file1.txt

ところが、実際にはエラーが表示され、その内容を読むと「./file が存在しない」「ss.txt が存在しない」と言われていることから、次のようになってしまっていることがわかります。

  • コマンド: ls
  • 第一引数: -l
  • 第二引数: ./file
  • 第三引数: ss.txt
  • 第四引数: ./file1.txt

つまり、xargs がスペース&改行区切りと解釈してしまっているのです。

解決法

上述の通り原因が「出力と入力で区切り文字が違う」ことなので、まっとうな解決法は「区切り文字を統一する」ということになります。これを基本方針として解決法を並べます。

区切り文字をヌル文字 (\0) に統一する

ググって真っ先に見つかった方法です。まずはこの方法を使用した例を提示します。

$ find . -type f -print0 | xargs -r -0 ls -l
-rw-r--r-- 1 user group    0 Feb 27 21:50 './file ss.txt'
-rw-r--r-- 1 user group    0 Feb 27 21:50  ./file1.txt

出力側の find では見つかったファイルの出力方法を指定できるのですが、-print0 によって「ヌル文字区切りで出力する」という指定になります。それを受け取る側の xargs では -0 を指定することで、入力文字列をヌル文字で区切って、そのそれぞれを引数として後続のコマンドに受け渡します。それ以外の文字は区切りと解釈されません。このように出力と入力で区切り文字を統一できているので、意図したとおり find で見つけたすべてのファイルについて ls -l の引数として使用されています。

ヌル文字を区切りに指定する方法は各コマンドで用意されています。

出力側

コマンド 方法 補足
find 引数で -print0 を指定する。 find では出力フォーマットを指定できる。デフォルトでは改行区切り。
echo オプション引数 -en を指定したうえで区切り位置に \0 を入れる。(例: echo -en 'abc\0ABC\0') デフォルトで末尾に入る改行は -n オプションで消せる。-e オプションを指定するとエスケープシーケンスを解釈するようになるので、引数内の \0 がヌル文字と解釈される。スペースが勝手にヌル文字に変換されるわけではないので注意。
sed オプション引数 -z を指定する。 sed は通常行単位で処理するため改行で区切るが、この方法でヌル文字区切りにできる。これを使用すると sed への入力もヌル文字区切りとして解釈されるため注意。

入力側

コマンド 方法 補足
xargs オプション引数 -0 デフォルトでは改行とスペース区切り1
awk オプション引数 -F '\0' フィールドの区切り文字を -F で指定できる。デフォルトではスペース区切り (改行はレコードの区切り)。これを指定しても awk'{print $1,$2}' によって出力した際のフィールド区切り文字はスペース1字になる。
sed オプション引数 -z を指定する。 sed は通常は行単位で処理するため改行で区切るが、この方法でヌル文字区切りにできる。これを使用すると sed の出力もヌル文字区切りのままになるので注意。

区切り文字を改行 (\n) に統一する

ヌル文字を区切り文字にする方法で解決できるのですが、パイプで受け渡していたデータを一度ファイル出力したい場合には不都合が生じます。以下の例では、ヌル文字区切りで出力した文字列を filelist.txt へ書き込んでいますが、その内容を cat で表示すると区切りが表示されません。

$ find . -type f -print0 > filelist.txt
$
$ cat filelist.txt
./file ss.txt./file1.txt
$
$ xargs -r -0 ls -l < filelist.txt
-rw-r--r-- 1 user group    0 Feb 27 21:50 './file ss.txt'
-rw-r--r-- 1 user group    0 Feb 27 21:50  ./file1.txt

これを回避するためには、ヌル文字以外で区切りを統一する必要が出てきますが、もっとも単純に考えれば改行区切りで統一するのが筋でしょう (問題提起の段階では改行区切りを想定していましたし)。

出力側の find は何も考えなければ改行区切りになるので、入力側の xargs だけ手を加えます。

$ find . -type f > filelist.txt
$
$ cat filelist.txt
./file ss.txt
./file1.txt
$
$ xargs -r -d '\n' ls -l < filelist.txt
-rw-r--r-- 1 user group    0 Feb 27 21:50 './file ss.txt'
-rw-r--r-- 1 user group    0 Feb 27 21:50  ./file1.txt

上のケースでは出力側はデフォルトのまま改行区切りにし、入力側を -d '\n' オプションで改行区切りにすることで、区切り文字を統一しています。

終わりに

シェルでは「コマンドの引数はスペースで区切る」に始まり、スペースで文字列で区切る例が多々見られるように感じます。なので、文字列の入出力の際は下記のことに留意するようにしています。2

  • スペースが混ざることはないか
  • スペースが混ざっても問題ないか

問題があれば上で述べたように区切り文字からスペースを除外することで解決できるか考えます。

for ループの区切り文字についても言及を考えましたが、まとまりを欠きそうなので割愛。(while read とか IFS とか)

参考

  1. 他の空白っぽい文字も区切りに使われているかも(未確認) 2

  2. 実際にはこの記事で主眼を置いていない問題が山ほどあるので、スペースだけケアすればよいわけではありません。同様のケアを必要に応じて他の文字にも行います。

1
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
1
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?