LoginSignup
8
8

More than 5 years have passed since last update.

print0 コマンド

Last updated at Posted at 2017-05-13

find のオプションのあれ

find コマンドのオプションに -print0 があります。みなさん大好きだと思います。

釈迦に説法ですが、主な用途は

$ touch euu\ upp\\ntotoro1
$ touch euu\ upp\\ntotoro2
$ ls euu*
euu upp\ntotoro1  euu upp\ntotoro2

$ find ./ -name 'euu*' -print |xargs rm
rm: `./euu' を削除できません: そのようなファイルやディレクトリはありません
rm: `uppntotoro2' を削除できません: そのようなファイルやディレクトリはありません
rm: `./euu' を削除できません: そのようなファイルやディレクトリはありません
rm: `uppntotoro1' を削除できません: そのようなファイルやディレクトリはありません

上記の様な空白などが混じった名前のファイル名を消去する時、 xargs は標準入力から空白や改行コードを区切り文字として読み込みます。よってファイル名の空白も区切り文字として解釈され、解釈された文字列群が rm の引数に渡されます。よって上記の様なエラーが起こります。

だからその現象を回避するために

$ find ./ -name 'euu*' -print0 |xargs -0 rm
$ ls
$

find-print0 オプションで ヌル文字を区切り文字として出力し、 xargs
-0 オプションで、ヌル文字を区切り文字と解釈して rm に引数として渡します。これで空白が混じったファイル名を無事に処理する事が出来ます。

以上が一般的なあらすじですが、以下の様に処理を行いたいです。

$ touch euu\ upp\\ntotoro1
$ touch euu\ upp\\ntotoro2
$ ls euu* | xargs rm
rm: `euu' を削除できません: そのようなファイルやディレクトリはありません
rm: `uppntotoro1' を削除できません: そのようなファイルやディレクトリはありません
rm: `euu' を削除できません: そのようなファイルやディレクトリはありません
rm: `uppntotoro2' を削除できません: そのようなファイルやディレクトリはありません

当然エラーになりますが、 find じゃなく ls を使いたいよね。というか ls の出力に限らず、空白が混じったファイル名が存在する事を考慮して ファイル名同士の区切り文字はヌル文字に変えたいよね。

つまり find の -print0 オプションに相当する print0 コマンド が欲しい のです。

Ruby で実装、実行

実装といっても骨格だけですが

  • print0.rb
#!/usr/bin/env ruby

while line = gets
  print "#{line.chomp}\0"
  STDOUT.flush
end

String#chomp は ( https://docs.ruby-lang.org/ja/2.4.0/class/String.html#I_CHOMP )

chomp(rs = $/) -> String[permalink][rdoc]
self の末尾から rs で指定する改行コードを取り除いた文字列を生成して返します。 
ただし、rs が "\n" ($/ のデフォルト値) のときは、 実行環境によらず "\r", "\r\n", "\n" の
すべてを改行コードとみなして取り除きます。

ですので、末尾の改行コードらしきものは全て取り除きます。実用上は何か問題はあるかな? 思いつかない。

そして実行。

$ ls euu* | print0.rb | xargs -0 rm
$ ls
$

期待した動作です。

シェルスクリプトで実装、実行

  • print0.sh
#!/usr/bin/env bash

trap 'echo Error: $0:$LINENO stopped; exit 1' ERR INT
set -eu

while read -r line
do
    echo -E -n "$line"
    echo -e -n "\0"
done

実行。

$ touch euu\ upp\\ntotoro1
$ touch euu\ upp\\ntotoro2
$ ls euu*
euu upp\ntotoro1  euu upp\ntotoro2

$ ls euu* | print0.sh |xargs -0 rm
$ ls
$

期待した動作です。

alias を設定、実行

シェルスクリプトが短く出来ているので ワンライナーにしてエイリアスしてみます。

  • print0 にエイリアスする
alias print0='while read -r line; do; echo -E -n "$line"; echo -e -n "\0"; done'

実行。

$ touch euu\ upp\\ntotoro1
$ touch euu\ upp\\ntotoro2
$ ls euu*
euu upp\ntotoro1  euu upp\ntotoro2

$ ls euu* | print0 |xargs -0 rm
$ ls
$

期待した動作です。

とは言え、個人的には エイリアスにするのは好みではありません。エイリアスが コマンドに勝手に展開されるとか、history に記録する際に展開されるとか zsh に設定がありそうな。(それが好みの人もいるかもしれませんが)

最後に

誰でも思いつく上 骨格だけならとても小さなツールですが 使い勝手は良い様な気がします。print0 なら大抵の シェルユーザーには "find のあれ" で通用するでしょうし。

8
8
4

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
8
8