cp コマンドは copy の略であり、ファイルやディレクトリをコピーするコマンドです。これに -r という recursive の略である再帰を示すオプションを組み合わせることであるディレクトリ以下をまとめてコピーできます。このcp -r
ですが、コピー先のディレクトリが存在する時としない時で挙動が変わります。具体的には次の様になります。
[shellscript]
# 初期状態。コピー元の a/b.txt のみがあり、コピー先の x はない
$ tree
.
└── a
└── b.txt
1 directory, 1 file
# cp -r a x を実行
# ディレクトリ x が生成され、x の中に a の中身が再帰的にコピーされる
$ cp -r a x
$ tree
.
├── a
│ └── b.txt
└── x
└── b.txt
2 directories, 2 files
[/shellscript]
[shellscript]
# 初期状態。コピー元の a/b.txt とコピー先の x の両方がある
$ tree
.
├── a
│ └── b.txt
└── x
2 directories, 1 file
# cp -r a x を実行
# x の中に a の中身が再帰的にコピーされる
$ cp -r a x
$ tree
.
├── a
│ └── b.txt
└── x
└── a
└── b.txt
3 directories, 2 files
[/shellscript]
困ったことに例の様にコマンド完了後のディレクトリ構成が異なります。開発環境等の何がしかのビルドコマンドにシェルを使うとしばしばこの問題が起きます。これの対処法を紹介します。
一つ目は -T オプションです。これを使うと次の様にディレクトリがあってもない時と同様にふるまいます。
[shellscript]
$ tree
.
├── a
│ └── b.txt
└── x
2 directories, 1 file
$ cp -rT a x
$ tree
.
├── a
│ └── b.txt
└── x
└── b.txt
2 directories, 2 files
[/shellscript]
Tオプションの説明は次の様になっており、ディレクトリだからその下に~などとせず、ファイルとして扱うとあります。
-T, --no-target-directory treat DEST as a normal file
とはいえ既存ファイルを削除する様なことはしません。
[shellscript]
$ tree
.
├── a
│ └── b.txt
└── x
├── b.txt
└── c.txt
2 directories, 3 files
# b.txt のみ入っているディレクトリ a を b.txt,c.txt のある x にコピー
$ cp -rT a x
$ tree
.
├── a
│ └── b.txt
└── x
├── b.txt
└── c.txt
2 directories, 3 files
[/shellscript]
二つ目はあらかじめディレクトリを作成しておく方法です。次の様にあらかじめ mkdir すれば必ずディレクトリがある時の挙動をします。
[shellscript]
# ;で区切ることにより mkdir が失敗しても cp まで実行されます。
mkdir x; cp -r a x
[/shellscript]
三つ目としてあらかじめディレクトリを削除する方法がありますが、事故が起きた時のリスクが大きいのであえてそうする必要もないかと思います。とはいえ更新系として何か作るなら一考の余地はあります。