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 のあれ" で通用するでしょうし。