できない.dev

ファイルをコピーするには

ファイルを別の場所へ複製する基本形を各言語で示す。
宛先が既にあるとき黙って上書きするか失敗するかは言語ごとに既定が違うので、その分岐まで含めて扱う。

公開:

各言語見出しの横のバッジは検証状態を表す。実行確認済みはコードを実際に実行して確認したもの、静的確認は構文と公式 API ドキュメントで確認したものである。

Python 実行確認済み

import shutil
 
# 宛先が既にあれば黙って上書きされる
shutil.copy2("report.csv", "backup/report.csv")
 
# 宛先にディレクトリを渡すと、その中へ同じ名前で作られる
shutil.copy2("report.csv", "backup/")

shutil.copy2 は中身に加えて更新時刻やパーミッションまで複製するので、バックアップ用途では copy より適している。
宛先の親ディレクトリは自動では作られず、無いまま呼ぶと FileNotFoundError になるため、必要なら先に os.makedirs で用意する。

JavaScript 実行確認済み

import { copyFile, constants } from "node:fs/promises";
 
// 既定では宛先を黙って上書きする
await copyFile("report.csv", "backup/report.csv");
 
// 上書きさせたくないときは COPYFILE_EXCL を渡す
try {
  await copyFile("report.csv", "backup/report.csv", constants.COPYFILE_EXCL);
} catch (err) {
  if (err.code !== "EEXIST") throw err;
  console.log("すでにある");
}

copyFile は既定で宛先を上書きするため、事故を避けたいときは第 3 引数に COPYFILE_EXCL を渡して既存時に EEXIST で失敗させる。
親ディレクトリが無いときの ENOENT まで握り潰さないよう、catch では err.code を見てから再 throw する。

TypeScript 実行確認済み

import { copyFile, constants } from "node:fs/promises";
 
await copyFile("report.csv", "backup/report.csv");
 
try {
  await copyFile("report.csv", "backup/report.csv", constants.COPYFILE_EXCL);
} catch (err) {
  if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
  console.log("すでにある");
}

書き方は JavaScript と同じだが、catch が受け取る err は unknown 型なので、code を読む前に NodeJS.ErrnoException へ絞り込む必要がある。
ここを any で通すと、せっかく型がある環境でプロパティ名の綴り間違いが素通りしてしまう。

うまくいかない時: TypeScript で catch の「Object is of type 'unknown'」が解決できない

Go 実行確認済み

src, err := os.Open("report.csv")
if err != nil {
	log.Fatal(err)
}
defer src.Close()
 
// O_EXCL を外すと既存ファイルを切り詰めて上書きする
dst, err := os.OpenFile("backup/report.csv", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
if errors.Is(err, fs.ErrExist) {
	fmt.Println("すでにある")
	return
}
if err != nil {
	log.Fatal(err)
}
defer dst.Close()
 
if _, err := io.Copy(dst, src); err != nil {
	log.Fatal(err)
}

標準ライブラリにファイルコピー専用の関数は無いので、開いた 2 つのファイルを io.Copy でつなぐ。
上書きの可否は開くときのフラグで決まり、os.Create は切り詰めて開く動作なので、既存を守りたいときは O_EXCL を付けた os.OpenFile を選ぶ。

Rust 実行確認済み

use std::fs;
use std::io;
 
// fs::copy は宛先が既にあっても黙って上書きし、書いたバイト数を返す
let bytes = fs::copy("report.csv", "backup/report.csv")?;
println!("{bytes}");
 
// 上書きさせたくないときは create_new で開いてから中身を流し込む
match fs::OpenOptions::new().write(true).create_new(true).open("backup/copy.csv") {
    Ok(mut dst) => {
        io::copy(&mut fs::File::open("report.csv")?, &mut dst)?;
    }
    Err(e) if e.kind() == io::ErrorKind::AlreadyExists => println!("すでにある"),
    Err(e) => return Err(e),
}

fs::copy はパーミッションも複製したうえでコピーしたバイト数を返すが、宛先の存在は確かめないので上書きを避けたい場面には向かない。
既存を守るなら create_new(true) で開き、AlreadyExists だけを他のエラーと分けて扱う。

Java 実行確認済み

import java.nio.file.*;
 
Path src = Path.of("report.csv");
Path dst = Path.of("backup/report.csv");
 
// REPLACE_EXISTING を外すと FileAlreadyExistsException になる
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
 
try {
    Files.copy(src, dst);
} catch (FileAlreadyExistsException e) {
    System.out.println("すでにある: " + e.getFile());
}

Files.copy の既定は「宛先があれば例外」で、上書きしたいときだけ REPLACE_EXISTING を明示する設計になっている。
更新時刻などの属性も引き継ぎたい場合は COPY_ATTRIBUTES を併せて渡す。

C# 実行確認済み

using System.IO;
 
// overwrite: true を外すと宛先がある場合に IOException になる
File.Copy("report.csv", "backup/report.csv", overwrite: true);
 
try
{
    File.Copy("report.csv", "backup/report.csv");
}
catch (IOException)
{
    Console.WriteLine("すでにある");
}

File.Copy は 2 引数版が宛先の存在を許さず、上書きするかどうかを 3 引数版で明示させる設計になっている。
親ディレクトリは作られず、無いまま呼ぶと DirectoryNotFoundException になるので、必要なら Directory.CreateDirectory を先に呼ぶ。

つまずき

同じ「コピー」でも、宛先が既にあるときの既定が言語ごとに違う。
Python の shutil.copy2、JavaScript の copyFile、Rust の fs::copy は黙って上書きするが、Java の Files.copy と C# の 2 引数版 File.Copy は例外を投げる。
既定に任せたまま言語をまたいで移植すると、片方では上書き事故、もう片方では実行時例外という別々の形で現れる。

宛先にディレクトリを渡せるのは Python だけ

shutil.copy2 は宛先がディレクトリなら、その中へ元のファイル名で作ってくれる。
ここで挙げた他の言語はいずれも宛先をファイルパスとして受け取るため、移植するときは宛先のファイル名まで自分で組み立てる必要がある。

親ディレクトリは作られない

どの言語でもコピー先の親ディレクトリは自動では作られない。
無いまま呼ぶと Python は FileNotFoundError、Node.js は ENOENT、C# は DirectoryNotFoundException で失敗する。
バックアップ先を日付ごとのディレクトリに分けるような処理では、コピーの前に作成をひと手間入れておく。

この記事は役立ちましたか?