find/xargsを使ったファイル・ディレクトリ名の一括置換/一括作成コマンド一覧



業務で楽するためのUNIXテクニック集 ファイル・ディレクトリ名の一括置換/一括作成

この記事は2008年~2009年頃に翔泳社で連載していた「業務で楽するためのUNIXテクニック集」の原本です。

はじめに

 この連載では、UNIX系OSをコマンドライン上から効率よく利用するために知っておくべきテクニックや、便利な小技を紹介していきます。

 今回は、第0回、第1回で紹介したfind/xargsコマンド、シェルの変数展開、制御構造を利用した「ファイル・ディレクトリ名の一括置換」に関するテクニックを紹介します。

 また、「ファイルやディレクトリの一括作成方法」「シンボリックリンクの作成方法」に関するテクニックも紹介します。「ファイル名の置換」テクニックを検証する際の、コマンド確認環境構築にご利用ください。

スポンサードリンク

関連記事

ファイル名置換に関する便利なUNIXテクニック例

 コマンドやシェルの制御構造を利用したいくつかの例を紹介しておきます。業務に合わせて応用しながら使ってください。

 また、誤ってファイルを上書きするのを防ぐには、mvコマンド/cpコマンドに「-i」「-b」オプションを付けて実行するか、「ファイルの一括作成」「シンボリックリンクの作成」で紹介したテクニックを利用して事前に検証環境を構築し、動作確認を行ってください。

サブディレクトリに渡るすべてのファイル名だけに拡張子「.bak」を付ける

$ find . -type f -print0 | while read -r -d '' file; do mv "$file" "$file.bak"; done

 xargsコマンドを利用する場合は、次のように指定できます。

$ find . -type f -print0 | xargs -0 -I% mv % %.bak

 findコマンドのアクションを利用する場合は、次のように指定できます。

$ find . -type f -exec mv '{}' '{}.bak' \;

同一階層内のすべてのディレクトリ・ファイル名から拡張子「.bak」を取り除く

$ find * -maxdepth 0 -name "*.bak" -print0 | while read -r -d '' file; do mv "$file" "${file%%.bak}"; done

 シェルの変数展開「${パラメータ%%パターン}」を用いて、拡張子名「.bak」を取り除いています。

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -name "*.bak" -print0 | perl -pe 's/\.bak\0/\0/g' | xargs -0 -I% mv %.bak %

同一階層の複数のファイル名の拡張子「.text」を「.txt」に置き換える

$ for file in *.text; { mv "$file" "${file%%.text}.txt"; }

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -print0 | perl -pe 's/\.text\0/\0/g' | xargs -0 -I% mv %.text %.txt

 FedoraやCygwinなどrenameコマンドが使える環境では、より高速にファイル名の置換が可能です(改行文字が含まれるファイル名は置換されません)。

$ rename .text .txt  *.text
※注5

renameコマンドを使うと、「test.text.text」のような二重拡張子を持つファイル名の場合、「test.txt.text」のように最初の拡張子名が置換されてしまいます。

$ ls
test.text.text
$ rename .text .txt  *.text
$ ls
test.txt.text

サブディレクトリに渡るすべてのファイルの拡張子「.text」を「.txt」に置き換える

$ find . -type f -name "*.text" -print0 | while read -r -d '' file; do mv "$file" "${file%%.text}.txt"; done

 シェルの変数展開「${パラメータ%%パターン}」を用いて、拡張子名「.text」を取り除いています。

 xargsコマンドを利用する場合は、次のように指定できます。

$ find . -type f -name "*.text" -print0 | perl -pe 's/\.text\0/\0/g' | xargs -0 -I% mv %.text %.txt

サブディレクトリに渡るすべてのファイルの拡張子を「.txt」に変更する

$ ls
test A.txt  test".txt  test.txt.bak

$ find . -type f -print0 | while read -r -d '' file; do mv "$file" "${file%.*}.txt"; done

$ ls
test A.txt  test".txt  test.text.txt

 シェルの変数展開「${パラメータ%パターン}」を用いて、一度拡張子を取り除いてから、拡張子を付け足しています。「%%」ではなく「%」とすることで、二重拡張子のファイルでも、最後の拡張子のみを置換してくれます。

サブディレクトリに渡る拡張子のないすべてのファイルに「.txt」を付ける

$ find * -type f ! -name "*.*" -print0 | while read -r -d '' file; do mv "$file" "$file.txt"; done

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -type f ! -name "*.*" -print0 | xargs -0 -I% mv % %.txt

 findコマンドのアクションを利用する場合は、次のように指定できます。

$ find . -type f ! -name "*.*" -exec mv '{}' '{}.bak' \;

サブディレクトリに渡るすべてのディレクトリ・ファイル名に拡張子「.bak」を付ける

$ find * -print0 | sort -rz | while read -r -d '' file; do mv "$file" "$file.bak"; done

 最初にディレクトリ名が置換されると、そのディレクトリ内のファイル名置換が正しく行われません。そのため、ディレクトリ名の置換は最後に行うよう、sortコマンドの「-r」オプションで検索結果を逆順に並べています。

 sortコマンドの「-z」オプションは、「\0」(ヌル文字)を区切り文字とするために付けています。「find -print0」や「xargs -0」などと組み合わせて使う場合に必要なオプションです。

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -print0 | sort -rz | xargs -0 -I% mv % %.bak

同一階層のすべてのファイル名の先頭に「日付-」を加える

$ ls
test A.txt  test".txt  test.text.bak

$ for file in *; { mv "$file" "$(date +%F)-$file"; }

$ ls
2009-02-09-test A.txt  2009-02-09-test".txt  2009-02-09-test.text.bak

 dateコマンドの「+%F」オプションにより、「yyyy-mm-dd」形式の日時を出力しています。

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -type f -print0 | xargs -0 -I% mv % "$(date +%F)"-%

 findコマンドのアクションを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -type f -exec  mv '{}' "$(date +%F)"-'{}' \;

サブディレクトリに渡るすべてのファイル名の拡張子の前に「日付-」を加える

$ ls
test A.txt  test".txt  test.text.bak

$ find . -type f -print0 | while read -r -d '' file; do mv "$file" "${file%.*}-$(date +%F).${file##*.}"; done

$ ls
test A-2009-02-09.txt  test"-2009-02-09.txt  test.text-2009-02-09.bak

 拡張子を取り除いて日付を付加し、最後に拡張子を加えています。ここでは、シェルの変数展開「${パラメータ##パターン}」を用いて、ファイル名の前方から「*.」にマッチする部分を除いています。

 なお、コマンドが長い場合は、次のように改行した指定もできます。

$ find . -type f -print0 | while read -r -d '' file;
> do mv "$file" "${file%.*}-$(date +%F).${file##*.}";
> done

 上記の「>」はコマンドがまだ完結していないときにシェルが表示する2次プロンプトです。

空のファイルを作成する

 サイズの大きくなったログファイルを空にしたい場合など、空(サイズが0)のファイルを作りたいことがあると思います。

 空ファイルを作成するには、次の3つの代表的な指定方法があります。

既存ファイルを空にする方法(1)

$ :> test1.txt

既存ファイルを空にする方法(2)

$ echo -n > test2.txt

既存ファイルを空にする方法(3)

$ cp /dev/null test3.txt

 (1)では、組み込みコマンドの「:」(何もせず終了コード0を返す)とリダイレクトを組み合わせています。

 (2)のように、echoコマンドとリダイレクトを組み合わせる方法でも作成できます。この際「-n」オプションを付けないと、改行コードが含まれた1byteのファイルが作成されてしまいます。

 (3)の「/dev/null」はヌルデバイスと呼ばれ、読み込んだ場合には常に空ファイルとなります。

 また、適当な名前の空のファイルを作成したい場合は、mktempコマンドを使用できます。

$ mktemp hoge.XXXXXX
hoge.jv2984

 ファイル名に「XXXXXX」のように大文字の「X」を6つ連続して指定すると、「X」の箇所に任意の文字列が挿入されたファイル名になります。なお「-d」オプションを付けると、ディレクトリが作成できます。

※注1

空のファイルを作る場合には、よくtouchコマンドが使われます。

$ touch test.txt

しかし、touchコマンドは指定した名前のファイル(上の場合だと「test.txt」)が既に存在する場合、ファイル内容は変更されず更新日付だけが新しくなります。

ファイル・ディレクトリの一括作成

ディレクトリを一度に複数作成する

 ファイルを複数のディレクトリに分けたい場合など、ディレクトリを一度に複数作成したいことがあると思います。作成したいディレクトリ名を中括弧「{」「}」で囲い、「,」区切りの指定をすることで作成できます。

 次の例では、「home」「lib」「bin」という名前のディレクトリを一度に作成しています。

$ ls
 ← ディレクトリは空
$ mkdir ./{home,bin,lib}
$ ls
bin/  home/  lib/

 ディレクトリ名に対して部分的に中括弧を利用した、次のような指定も可能です。

$ mkdir dir{1,2,3}
$ ls
dir1/  dir2/  dir3/

 また、一度に多階層のディレクトリを作成する場合には、mkdirコマンドに「-p」オプションを付けます。

$ mkdir -p dir1/dir2/dir3
$ find *
dir1
dir1/dir2
dir1/dir2/dir3

 mkdirコマンドの「-p」オプションと中括弧を組み合わせると、次のようなディレクトリ構成も一度に作成可能です。

$ mkdir -p ./{lib,src,doc/{html,pdf,txt}}
$ find *
doc
doc/html
doc/pdf
doc/txt
lib
src

 上記の例は、「lib」「src」「doc」という名前のディレクトリを作成し、さらに「doc」ディレクトリ以下に、「html」「pdf」「txt」という名前のディレクトリを作成しています。

シェルの制御構文を利用してディレクトリを一度に複数作成する

 より多くのディレクトリを作成する必要がある場合は、シェルの制御構文を利用して次のように指定できます(ファイルを作成したい場合は、mkdirコマンドをtouchコマンドなどに置き換えてください)。

$ for ((i=1; i<=5; i++)); { mkdir "test$i"; }
$ ls
test1/  test2/  test3/  test4/  test5/

 ただし上記の方法は、ループの回数だけmkdirコマンドが呼び出されるため、処理速度が遅くなります。より速くディレクトリを作成したい場合は、xargsコマンドを利用して次のように指定することが可能です。

$ for ((i=1; i<=5; i++)) { echo "test$i "; } | xargs mkdir
$ ls
test1/  test2/  test3/  test4/  test5/

 「"test$i (空白)"」となっていることに注意してください。あらかじめechoコマンドでディレクトリ名を出力しておき、mkdirコマンドを一度だけ実行させることで処理速度を速めています。xargsコマンドに関しては第1回を参考にしてください。

 なおbash 3.0以降であれば、次のように中括弧を利用することで、さらに高速にディレクトリを作成できます。

$ mkdir test{1..5}

 bash 3.0以降では{開始(数字or英字)..終了(数字or英字)}と指定すると、連続的な表現として扱ってくれます。このため、連番ファイルが生成されます。

ゼロパディングした名前のディレクトリを一度に複数作成する

 ファイル名・ディレクトリ名の長さを揃えて管理したい場合には、ゼロパディング(ゼロ埋め)を行うのが便利です。

 「dir001」「dir010」「dir100」のような名前のディレクトリを作りたい場合は、printfコマンドを利用して、次のように指定できます(ファイルを作成したい場合は、mkdirコマンドをtouchコマンドなどに置き換えてください)。

$ for ((i=1; i<=5; i++)) { printf "test%03d " $i; } | xargs mkdir

 bash 3.0以降では、次の指定も可能です。

$ for i in {1..5}; { printf "test%03d " $i; } | xargs mkdir

$ ls
dir001/  dir002/  dir003/  dir004/  dir005/

 「"test%03d (空白)"」となっていることに注意してください。printfコマンドは、C言語のprintf関数のように、指定のフォーマットで文字列を出力するコマンドです。「%03d」と指定すれば、3桁でゼロパディングされます。
シンボリックリンクのファイルを一括で作成する

 シンボリックリンクのファイル名を置き換えても、リンク元のファイル名は変更されません。シンボリックリンクのファイルを作成するには、lnコマンドと「-s」オプションを利用するのが一般的です。

$ ln -s [リンク元ファイル] [リンクファイル]

 しかし、シンボリックリンクのファイルを一括で作成するには、cpコマンドが便利です(FreeBSDは、cpコマンドは未対応)。

$ cp -rs [リンク元ファイル] [リンクファイル]

 「-r」オプションでディレクトリを再帰的にコピーして、「-s」オプションでシンボリックリンクファイルを作成しています。

 例えば ホームディレクトリに存在する「tmp」ディレクトリへ、「tmp2」という名前でシンボリックリンクを作るには、チルダ展開を利用すれば次のように指定できます。

$ cp -rs ~/tmp ~/tmp2

ファイル名・ディレクトリ名の一括置換

 ここでは、ファイル名やディレクトリ名の一括置換のテクニックを紹介します。なお、UNIX系OSでは、「/」(スラッシュ)と「\0」(ヌル文字)の文字はファイル名やディレクトリ名として使用できません。

※注2

注意書きのないコマンド例に関しては、「\n」(改行)や「\」(バックスラッシュ)、「"」(ダブルクォーテーション)などが含まれているファイル名・ディレクトリ名も置換できることを確認しています。
※注3

Windows NT系(2000, XP, Vista)のOSでは、Unicodeを内部コードとして利用しているため、ラテン文字などもファイル名・ディレクトリ名に使用できます。
ただし、Cygwin ではCP932(≒SJIS)で扱えない文字列がファイル名・ディレクトリ名に含まれると正しく処理できません。

同一階層内のすべてのファイル・ディレクトリ名に拡張子を付ける

 下記のように拡張子を持たないファイルが複数存在しているとします。

$ ls
test1  test2  test3  test4  test5

 このファイルに、すべて「.bak」の拡張子をつけるには、シェルの制御構文を利用し、次のように指定できます。

$ for file in *; { mv "$file" "$file.bak"; }

 xargsコマンドを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -print0 | xargs -0 -I% mv % %.bak

 findコマンドのアクションを利用する場合は、次のように指定できます。

$ find * -maxdepth 0 -exec mv '{}' '{}.bak' \;

 さらに、Debianではperlにより実装されたrenameコマンドが用意されています。第1引数にperl実行文が利用できるため、汎用的な指定ができます(改行文字が含まれるファイル名は置換されません)。

$ rename 's/$/.bak/' *

サブディレクトリに渡るすべてのファイル名から拡張子を取り除く

 サブディレクトリに渡るファイル名から拡張子「.bak」を取り除くには、findコマンドとシェルの制御構文を利用して、次のように指定できます。

$ find . -type f -name "*.bak" -print0 | while read -r -d '' file; do mv "$file" "${file%%.bak}"; done

 上記の例では、シェルの変数展開「${パラメータ%%パターン}」を用いて、ファイル名の後方から「.bak」にマッチした部分を除いています。シェルの変数展開に関しては、第0回を参考にしてください。

 加えて、組み込みコマンドreadとシェルの変数展開を利用しています。

 「-r」オプションは、ファイル名に「\」(バックスラッシュ)が含まれている場合を考慮して付けています。また「-d」オプションで「\0」(ヌル文字)を区切り文字に設定しています(「-d $'\0'」と同様)。findコマンドのオプションに関しては第1回を参照してください。

 xargsコマンドを利用する場合は、次のように指定できます。

$ find . -type f -name "*.bak" -print0 | perl -pe 's/\.bak\0/\0/g' | xargs -0 -I% mv %.bak %

 perlを利用して「.bak\0」文字を「\0」(ヌル文字)に置換しています。また、「-p」オプションは繰り返し処理と出力を行い、「-e」オプションで与えられた文字列を実行します。

※注4

「perl -pe」の代わりに「sed -e」を利用すると、次のように指定できます(FreeBSDのsedコマンドには対応していません)。

$ find . -type f -name "*.bak" -print0 | sed -e 's/\.bak\x00/\x00/g' | xargs -0 -I% mv %.bak %

「\x00」は「\0」(ヌル文字)の16進数表記です、8進数表記の「\o000」でも構いません。
sedコマンド、perlの詳細な使い方に関しては、次回以降に説明します。

まとめ

 今回は「ファイル・ディレクトリ名の一括置換」や「ファイルやシンボリックリンクの一括作成」に関するテクニックを紹介しました。

 今回紹介した指定方法以外にも、コマンドやシェルの制御構造を利用することで、さまざまな指定ができると思います。ただし、特殊文字や空白が含まれるファイル名でも正しく動作するかを確認して業務で使うようにしてください。

 次回以降も、テーマごとにテクニックや小技を紹介していく予定です。