xargsにパイプで渡した標準入力が空だった場合におかしな動きをしないようにする

2021-01-25Bash

はじめに

xargs コマンドは非常に便利です。

パイプでつなぎ、前のコマンドを1つずつ(あるいはいくつかの束ずつ)処理していくときに
簡潔に記述できかつ高速です。

ただ、パイプの前のコマンドの実行結果が空になると想定しない挙動をする場合があります。
問題と対処について紹介します。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

$ xargs --version | head -n 1
xargs (GNU findutils) 4.5.11

何も考慮せず xargs を使ってみる。

なにか具体的な例を考えたいので、 seq コマンドで生成した数列を xargs に渡し、 Markdown記法で - を先頭に付与してみたいと思います。

# `seq 1 5` の実行結果(1 2 3 4 5)を1つずつ取り出し、 `echo '-' 1` のように実行していく
$ seq 1 5 | xargs -n 1 echo '-'
- 1
- 2
- 3
- 4
- 5

正しく動作しました。

ここでちょっと意地悪をしてみます。
seq 6 5 と言うコマンドを実行し、 xargs に実行結果を渡すようにしてみます。

$ seq 6 5 | xargs -n 1 echo '-'
-

なぜか - (ハイフン)が1つだけ出力されてしまいました。
受け取った標準入力が空っぽだった場合でも xargsecho '-' を最低1回は実行してしまうようです。

-r オプションを付与する

これを回避するために、 -r オプションを付与します。

# こちらはオプション無しのときと同様
$ seq 1 5 | xargs -r -n 1 echo '-'
- 1
- 2
- 3
- 4
- 5

# 何も出力されない
$ seq 6 5 | xargs -r -n 1 echo '-'

-r オプションですが、 ロングオプションとして --no-run-if-empty があります。
こちらのほうがわかりやすいので利用したいところですが、MacOS標準のxargsコマンドには --no-run-if-empty オプションがありません。
ショートオプションである -r はどのUnix系OSでも利用できるようですので(少なくともLinux、MacOS、Brewでインストールしたxargsでは使えました。)、
こちらを利用しておくのが無難でしょう。

ひとこと

「これをデフォルト挙動にしてほしかった」と言いたいところですが、以下のようなコマンドを実行する場合にはこの挙動が望ましいのでしょう。

# カレントディレクトリ配下にはファイルが一切ないものとします
$ find . -type f | xargs ls -ls
total 0

# カレントディレクトリ配下にはファイルが一切ないものとします
$ find . -type f | xargs du -hs
0       .

2021-01-25Bash