UNIXのcp
コマンドのように,java Copy1 from_file to_file
を
実行すると,from_file
の内容がto_file
にコピーされるようなコマンドCopy1
を
作成してください.to_file
が存在している場合は上書きしてください
(単純にFileWriter
で書き込むと上書きすることになります).
コマンドライン引数に必要な数のファイルが指定されない場合,エラーメッセージを出力しましょう. コマンドライン引数で指定されたものは,ファイルであるとしても構いません.
// 必要な import 文を書いてください.
public class Copy1{
void run(String[] args) throws IOException{
// コマンドライン引数に必要な分のファイルが指定されているか確認する.
// args.lengthが2より小さい場合,必要な引数が指定されていない旨を出力して終了する.
// argsの0番目,1番目の要素からそれぞれFile型の実体を作成する.
// copyメソッドを呼び出す.
}
void copy(File from, File to) throws IOException{
// fromをBufferedReader(FileReader)で開く.
// toをPrintWriter(FileWriter)で開く.
// fromから読み込んだ内容をtoに書き出す.
// ファイルの終わりまでこの処理を繰り返す.
// from, to から開いたストリームを閉じる.
}
// mainメソッドは省略.
}
$ echo 'abcdef' > file1 # file1 を作成する.
$ ls file2 # file2 が存在しないことを確認する.
ls: file2: No such file or directory
$ java Copy1 file1 file2 # file1 を file2 にコピーする.
$ cat file2 # file2 と file1 の内容が同じであることを確認する.
abcdef
$ java Copy1 # コマンドライン引数がない場合にエラーを出して終了する.
cp: コマンドライン引数には,少なくとも,コピー元,コピー先を指定する必要があります.
$ java Copy1 file2 # コマンドライン引数が足りない場合にエラーを出して終了する.
cp: コマンドライン引数には,少なくとも,コピー元,コピー先を指定する必要があります.
1. ファイルをコピーするでは,コマンドライン引数は2つ,かつ両方がファイルであることが前提でした.
ここでは,複数ファイルを一つのディレクトリにコピーするプログラムCopy2
を作成します.
java Copy2 from_file1 from_file2 from_file3 directory
を実行すると,from_file1
,from_file2
,from_file3
がディレクトリにコピーされます.
すなわち,directory/from_file1
,directory/from_file2
,directory/from_file3
が作成されます.
このような挙動を行うプログラムを作成してください.
まず,directory/from_file1
に書き込むためには,directory/from_file1
を表すFile
型の実体を作成しなければいけません.
そのためにまず,File
型の変数to
が出力先ディレクトリであるdirectory
を表し,
出力先のファイル名であるfrom_file1
を File
型の変数from
が表していると仮定します.
このとき,new File(to, from.getName())
という新たなFile
の実体を
作成することで,directory/from_file1
を表すFile
型の実体を作成できます.
コマンドライン引数の一番最後の要素がディレクトリであるか否かを判定してください. 一番最後の要素が存在し,ディレクトリである場合のみ,その前のコマンドライン引数が示すファイルの内容をディレクトリ以下にコピーしてください.
コマンドライン引数の一番最後の要素が存在しない場合,もしくは,ファイルであった場合は, 複数ファイルのコピーは行えませんので,エラーメッセージを出力し,終了してください.
void run(String[] args){
// コマンドライン引数に必要な分のファイルが指定されているか確認する.
// args.lengthが2より小さい場合,必要な引数が指定されていない旨を出力して終了する.
// 出力先は,コマンドライン引数の一番最後の要素である.
File to = new File(args[args.length - 1]);
if(.....){ // toが存在しない,もしくはファイルの場合.
// args.length が 2 より大きい場合,
// 複数ファイルを1つのファイルにコピーできない旨を出力し,終了する.
// そうでない場合,copyメソッドを呼び出す.
}
else if(....){ // toがディレクトリの場合.
for(....){ // argsを必要なだけ繰り返す.一番後ろの要素は省くことを忘れない.
File from = new File(arg[i]);
File toFile = new File(to, from.getName());
// fromをtoFileにコピーするよう copy メソッドを呼び出す.
}
}
}
$ for i in 1 2 3; do echo "file$i" > "file$i.txt" ; done # ファイルを作成する.
$ ls file?.txt
file1.txt file2.txt file3.txt
$ mkdir dir
$ java Copy2 file1.txt file2.txt dir
$ ls dir # dir の内容を確認する.
file1.txt file2.txt
$ java Copy2 file3.txt file4.txt # file3.txt を file4.txt にコピーする.
$ cat file4.txt
file3
$ java Copy2 file2.txt file3.txt file5.txt
cp: 複数ファイルを一つのファイルにコピーできません.
オプション解析を行うようにプログラムを改良しCopy3
を作成してください.
オプションとは,コマンドに渡す -h
などの-
から始まる文字(文字列)です.
オプションによってプログラムの挙動が少し変わります.
最終的に 6. ディレクトリを再帰的にコピーするを終えると, 次のようなオプションを含んだプログラムが完成することになります.
$ java Copy3 -h
Usage: java Copy3 [OPTIONS] from_file to_file
java Copy3 [OPTIONS] from_file ... to_directory
OPTIONS
-h: このメッセージを表示して終了する(help).
-i: コピー先のファイルが存在していた時,ユーザに上書き確認を求める(interactive).
-r: ディレクトリを再帰的にコピーする(recursive).
-u: コピー先のファイルが存在しない場合,もしくはコピー元のファイルの方が新しい場合のみコピーする(update).
-v: 実行内容を表示する(verbose).
オプション解析を行うために,Arguments.java
を作成してください.Arguments
には,Boolean
型の5つの変数,help
,interactive
,recursive
,update
,
そして,verbose
と,ArrayList<String>
型のlist
の6つのフィールドを
持たせてください.list
は宣言時に初期化を行うようにしてください(new
を使いArrayList<String>
の
実体を作成し,代入しておいてください).Boolean
型の変数も
全て false
で初期化しておいてください.
次の parse
メソッドを Arguments
に定義してください.
void parse(String[] args){
for(String arg: args){
if(Objects.equals(arg, "-h")){
this.help = true;
}
else if(Objects.equals(arg, "-i")){
this.interactive = true;
}
else if(Objects.equals(arg, "-r")){
this.recursive = true;
}
else if(Objects.equals(arg, "-u")){
this.update = true;
}
else if(Objects.equals(arg, "-v")){
this.verbose = true;
}
else{
this.list.add(arg);
}
}
}
次に,Copy3
クラスのrun
メソッドを次のように定義しましょう.
void run(String[] args){
Arguments arguments = new Arguments();
arguments.parse(args);
if(arguments.help){
this.printHelp();
}
else{
this.performCopy(arguments);
}
}
このメソッドのでは,-h
オプションが指定された時,printHelp
メソッドを呼び出すようになっています.
上記に示したヘルプメッセージを表示するようprintHelp
メソッドの処理を書いてください.
単純に上に示したメッセージをSystem.out.println
で出力すれば良いでしょう.
また,performCopy
はステップ2までの処理(run
)を基本として
書き直してください.String
型の配列であるargs
ではなく,ArrayList<String>
から
要素を取り出す必要があります.
また,-v
オプション(verbose)が指定された場合,どのファイルがどこにコピーされたのかを表示するようにしてください.
次のような処理になるでしょう.「コピー処理」部分はfrom
からto
へのコピーの処理に置き換えてください.
void copy(File from, File to, Arguments args) throws IOException{
// コピー処理
// ...
if(args.verbose){
System.out.printf("%s -> %s%n", from.getPath(), to.getPath());
}
}
$ for i in 1 2 3 ; do echo "file$i" > "file$i.txt" ; done
$ java Copy3 -v file1.txt hoge.txt # 実行状況を確認してコピーする.
file1.txt -> hoge.txt
$ mkdir dir2
$ java Copy3 -v file1.txt file2.txt file3.txt dir2 # 実行状況を確認してコピーする.
file1.txt -> dir2/file1.txt
file2.txt -> dir2/file2.txt
file3.txt -> dir2/file3.txt
ファイルの上書き確認を行うようプログラムを改良し,Copy4
を作成しましょう.
コピー先のファイルが存在し,ディレクトリでなく通常のファイルの場合(to.exists() && to.isFile()
)
ユーザに上書きして良いかの確認を取りましょう.
このプログラムを実現するには,copy
メソッドの最初に,isOverwrite
メソッドを呼び出し,
上書きするかを判定します.isOverwrite
メソッドでは,まずチェックすべきかどうか,すなわち,
出力先が存在しており,かつ,ファイルであるかを確認しています.
その上で,チェックすべきである場合は,interactive
オプションが指定されており,
ユーザが上書きしない場合に,isOverwrite
は false
を返すようにしています.
なお,ユーザへの確認処理はisOverwriteAskToUser
メソッドで行っています.
なお,SimpleConsole.java
は第9回目 練習問題2. 電話帳の作成と同じものを利用してください.
SimpleConsole.java
はこの文章をクリックすることでもダウンロードできます.
また,実際にコピーを行う処理をcopy
メソッド内からdoCopy
メソッドに移しています.
Boolean isOverwriteAskToUser(File to) throws IOException{
SimpleConsole console = new SimpleConsole();
while(true){
System.out.printf("%sを上書きしますか? (y/n [n]) ", to.getName());
String line = console.readLine();
line = line.trim(); // 入力された前後の空白を取り除く.
if(Objects.equals(line, "y")){
return true;
}
else if(Objects.equals(line, "") || Objects.equals(line, "n")){
// nもしくは単に改行された場合
System.out.println("上書きしません.");
return false;
}
else{ // その他の値が入力された場合.
System.out.println("yかnを入力してください.");
}
}
}
Boolean isOverwrite(File to, Arguments args) throws IOException{
if(to.exists() && to.isFile()){
if(args.interactive && !isOverwriteAskToUser(to)){
return false;
}
}
return true;
}
void copy(File from, File to, Arguments args) throws IOException{
if(isOverwrite(to, args)){
this.doCopy(from, to);
// verboseの処理
}
}
void doCopy(File from, File to){
// コピー処理
// ...
}
$ for i in 1 2 3 ; do echo "file$i" > "file$i.txt" ; done
$ java Copy4 file1.txt file2.txt
$ cat file2.txt
file1
$ java Copy4 -i file1.txt file3.txt # file3.txt が存在すれば上書き確認を行う.
file3.txtを上書きしますか? (y/n [n]) aaaa # 再入力可能かを確認するため,aaaa を入力する.
yかnを入力してください.
file3.txtを上書きしますか? (y/n [n]) n # nを入力して,上書きしない.
上書きしません.
$ cat file3.txt # 内容が書き換わっていないことを確認する.
file3
$ java Copy4 -i -v file1.txt file3.txt
file3.txtを上書きしますか? (y/n [n]) y # yを入力した.上書きコピーを実行する.
file1.txt -> file3.txt
$ cat file3.txt # 内容が書き換わったことを確認する.
file1
ファイルの更新日時を確認し,出力先のファイルの方が新しければコピーしないようにしましょう.Copy4
を
コピーしてCopy5
を作成し,処理を追加しましょう.
先ほど作成したisOverwrite
メソッドを修正して,この確認を行うようにしましょう.
Boolean isOverwrite(File from, File to, Arguments args) throws IOException{
if(to.exists() && to.isFile()){
Boolean overwriteFlag = true; // デフォルトは上書きする.
if(args.update){ // updateオプションが指定された場合
// toの方が新しい場合,上書きしない.
overwriteFlag = ... // 上書きしない.
}
// 上書き,かつ,インタラクティブであれば,ユーザに上書きの可否を問い合わせる.
// ユーザが上書きを指示しなければ上書きしない.
if(overwriteFlag && args.interactive && !isOverwriteAskToUser(to)){
overwriteFlag = false;
}
return overwriteFlag;
}
return true;
}
なお,Copy4
で作成したisOverwrite
メソッドに新しい条件を追加しています.
条件が複数になったため,overwriteFlag
というBoolean
型の変数で上書き確認を行なっています.
$ for i in 1 2 3 ; do echo "file$i" > "file$i.txt" ; done
$ touch file1.txt # file1.txt の最終更新日時を更新した.
$ java Copy5 -u -v file1.txt file3.txt # file3.txt の方が古いため,コピーする.
file1.txt -> file3.txt
$ java Copy5 -u -v file2.txt file1.txt # file1.txt の方が新しいため,コピーしない.
$ touch file2.txt
$ cat file2.txt
file2
$ java Copy5 -u -v -i file2.txt file1.txt # file1.txt の方が古いため,コピーする.
file1.txtを上書きしますか? (y/n [n]) n
上書きしません.
$ java Copy5 -u -v -i file2.txt file1.txt # file1.txt の方が古いため,コピーする.
file1.txtを上書きしますか? (y/n [n]) y
file2.txt -> file1.txt
$ cat file1.txt
file2
今までは,ファイルを出力先のファイル,もしくはディレクトリにコピーするコマンドを作成しました.
このステップでは,ディレクトリをコピーする処理を追加します.Copy5
をコピーし,Copy6
を作成してください.
ステップ2のヒントで作成したrun
メソッドでは,
出力先(to
)が,存在しない,もしくはファイルの場合と,出力先がディレクトリに場合に場合分けを行い,
それぞれで処理を実行していました.
ディレクトリを再帰的にコピーするには,copyRecursive
メソッドを用意します.
そこで,ディレクトリ内のファイル,ディレクトリを調べ,ファイルであれば,
すでに作成済みであるcopy
メソッドを呼び出します.
ディレクトリであれば,copyRecursive
の再帰呼び出しを行います.
ここで,copy
メソッドを呼び出すとき,出力先となるFile
型の変数に気をつける必要があります.
例えば,from
にaaa
が指定され,その中のaaa/bbb/file.txt
をdest
にコピーするとき,単純に new File(to, file)
とすると,dest/aaa/bbb/file.txt
に出力されることになります.
本来の出力は,dest/bbb/file.txt
であるはずです.そのファイル名を取得しているのが,createToFile
です.
void performCopy(Arguments args){
// コマンドライン引数に必要な文のファイルが指定されているか確認する.
// 出力先は,コマンドライン引数の一番最後の要素である.
File to = new File(args.list.get(args.list.size() - 1));
if(.....){ // toが存在しない,もしくはファイルの場合.
this.copyToFile(args, to);
}
else if(....){ // toがディレクトリの場合.
this.copyToDirectory(args, to);
}
}
void copyToDirectory(Arguments args, File to){
for(....){ // argsを必要なだけ繰り返す.一番後ろの要素は省くことを忘れない.
File from = new File(arg[i]);
// fromがファイルの場合
File toFile = new File(to, from.getName());
// copyメソッドを呼び出し,コピーする.
// コマンドライン引数に recursive が指定された場合.
// copyRecursiveメソッドを呼び出す.
this.copyRecursive(from, from, to, args);
else{ // その他の場合
System.out.printf("cp: %sはディレクトリです(コピーしません)%n", from.getName());
}
}
}
void copyToFile(Arguments args, File to){
// args.list.size() が 2 より大きい場合,
// 複数ファイルを1つのファイルにコピーできない旨を出力し,終了する.
// そうでない場合,fromがディレクトリか否かを調べる.
else{
File from = new File(arguments.list.get(0));
if(from.isDirectory()){ // from がディレクトリの場合.
// toがファイルの場合
System.out.println("cp: ディレクトリをファイルにコピーできません.");
// コマンドライン引数に recursive が指定された場合.
// copyRecursiveメソッドを呼び出す.
this.copyRecursive(from, from, to, arguments);
else{ // その他の場合
System.out.printf("cp: %sはディレクトリです(コピーしません)%n", from.getName());
}
}
else{ // fromがファイルの場合.
this.copy(from, to, arguments);
}
}
}
....
void copyRecursive(File base, File from, File to, Arguments args) throws IOException{
for(File file: from.listFiles()){
if(file.isDirectory()){
// copyRecursiveメソッドを再帰呼び出し.
}
else{
File toFile = this.createToFile(base, file, to);
// toFile の親ディレクトリ(File型)を取得する(toFile.getParentFile()).
// toFile の親ディレクトリが存在しない場合作成する(parent.mkdirs()).
this.copy(file, toFile, args);
}
}
}
File createToFile(File base, File from, File to){
String basePath = base.getPath();
String fromPath = from.getPath();
String newPath = fromPath.substring(basePath.length() + 1);
return new File(to, newPath);
}
$ mkdir dir1
$ for i in 1 2 3 ; do echo "file $i in dir1" > dir1/file$i.txt ; done
$ ls dir1
file1.txt file2.txt file3.txt
$ ls dir2
ls: dir2: No such file or directory
$ java Copy6 dir1 dir2
cp: dir1はディレクトリです(コピーしません)
$ java Copy6 -r -v dir1 dir2
dir1/file1.txt -> dir2/file1.txt
dir1/file2.txt -> dir2/file2.txt
dir1/file3.txt -> dir2/file3.txt