重複名のファイルを両方残して移動するには、移動前に、移動元の各ファイルに未使用の移動先名を付けます。このガイドでは、番号付きの接尾辞を追加するMacのショートカットを作成し、使い捨てのテキストファイルで結果を確認します。
ここでは、同じ名前のファイルがすでに存在する可能性のある別のローカルフォルダへ、通常のファイルを移動します。移動先にある既存のファイルはそのまま残り、移動元のファイルには notes (2).txt のような名前が付けられます。両方を残すとは、移動後に両方のファイルが移動先にあるという意味です。移動元のファイルは元のフォルダには残りません。
両方のファイルを残すことについて、Appleは何を説明していますか?
Appleは、ショートカット内でシェルスクリプトを直接実行できることを説明しています。ここで引用するAppleの公開ショートカット関連ページでは、Move File アクションの競合時の処理として Keep Both が指定できるとは説明されていません。文書化されていない設定に頼る代わりに、このワークフローでは Run Shell Script で名前を明示的に決定します。AppleはmacOS用ショートカットの紹介でスクリプト機能について説明しています。
macOSの基盤となるコマンドで文書化されている動作は、より限定的です。mv -n は既存のファイルを上書きしませんが、新しい名前は生成しません。そのため、このショートカットには、ファイル名を選ぶループと上書きを防ぐオプションの両方が必要です。Appleが公開している mv(1) のマニュアル原文に示されているとおり、これらは別々の役割です。
Finderでのフォルダの結合にも制限があります。Appleによると、片方のフォルダにもう片方にはない項目が含まれている場合にのみ Merge が表示されます。同名の異なるバージョンのファイルが入ったフォルダでは、Stop または Replace が表示されます。そのため、フォルダの結合では、ここで必要な連番付きファイル名の動作は実現できません。Macでフォルダ内のファイルを整理するを参照してください。
使い捨ての重複名の例を用意する
同じローカルディスク上にある2つのフォルダを使って、この例を実行します。対象は、移動元フォルダ直下にある通常の非表示ではないファイルです。サブフォルダ内を再帰的に処理したり、エイリアスやシンボリックリンクを移動したり、パッケージを結合したりすることはありません。2つのフォルダを別のプロセスが変更していない状態で、実行は一度に1つだけ行ってください。
このショートカットを使うには、Macで Run Shell Script が利用可能で、実行できる必要があります。お使いの環境でこのアクションを実行できない場合は、この記事の後半で説明するFinderの手順を使ってください。以下の例には追加のソフトウェアは必要ありません。
ターミナルを開き、次のセットアップコマンド全体を貼り付けてください。合成テキストだけを含む新しいデスクトップフォルダが作成されます。Shortcuts-name-demo がすでに存在する場合、コマンドは内容を再利用せずに停止します。
/bin/bash <<'BASH'
set -e
root="$HOME/Desktop/Shortcuts-name-demo"
/bin/mkdir "$root"
/bin/mkdir "$root/Incoming" "$root/Collected"
printf '%s\n' 'Incoming synthetic notes.' > "$root/Incoming/notes.txt"
printf '%s\n' 'Existing synthetic notes.' > "$root/Collected/notes.txt"
printf '%s\n' 'Earlier synthetic second copy.' > "$root/Collected/notes (2).txt"
printf '%s\n' 'Synthetic readme.' > "$root/Incoming/readme.txt"
printf 'Example folder: %s\n' "$root"
BASH
意図的に競合を作っています。移動先にはすでに notes.txt と notes (2).txt の両方が入っています。単に (2) を追加するだけのショートカットでは、また競合が発生します。候補を順に確認し、未使用の名前が見つかるまで試すのが有効なルールです。
確認しやすくするには、Finderの 設定 > 詳細 で すべてのファイル名拡張子を表示 を有効にしてください。Appleの説明によると、拡張子を隠すと表示が変わりますが、拡張子そのものを変更すると、ファイルを開くアプリに影響する場合があります。この例では .txt をそのままにしてください。Macでファイル名拡張子を表示または非表示にするを参照してください。
競合を明示的に処理するショートカットを作成する
ショートカットアプリで追加ボタンを使ってショートカットを作成し、ファイルを移動して両方残す という名前を付けます。アクションライブラリで Run Shell Script を検索して追加します。AppleはMacでカスタムショートカットを作成するで、ショートカットの作成とアクションの追加について説明しています。
アクションのシェルを /bin/bash に設定し、サンプルスクリプトを以下のプログラム全体に置き換えます。このバージョンでは、スクリプト内の変数から2つのフォルダのパスを読み込みます。ショートカットの入力、選択したFinder項目、標準入力は使用しないため、入力の受け渡し設定は関係ありません。
set -e
shopt -s nullglob
src_dir="$HOME/Desktop/Shortcuts-name-demo/Incoming"
dst_dir="$HOME/Desktop/Shortcuts-name-demo/Collected"
fail() {
printf '%s\n' "$1" >&2
exit 1
}
[[ -d "$src_dir" ]] || fail "Source folder does not exist."
[[ -d "$dst_dir" ]] || fail "Destination folder does not exist."
src_dir=$(cd "$src_dir" && pwd -P)
dst_dir=$(cd "$dst_dir" && pwd -P)
[[ "$src_dir" != "$dst_dir" ]] || fail "Choose different folders."
moved=0
for src in "$src_dir"/*; do
if [[ -L "$src" || ! -f "$src" ]]; then
printf 'Skipped non-regular item: %s\n' "$src" >&2
continue
fi
name=${src##*/}
stem=$name
ext=
if [[ "$name" == *.* && "$name" != .* && "$name" != *. ]]; then
stem=${name%.*}
ext=.${name##*.}
fi
target="$dst_dir/$name"
number=2
while [[ -e "$target" || -L "$target" ]]; do
target="$dst_dir/$stem ($number)$ext"
number=$((number + 1))
done
/bin/mv -n "$src" "$target" || fail "Move failed: $src"
if [[ -e "$src" || -L "$src" || ! -f "$target" ]]; then
fail "Move was not completed as expected: $src"
fi
moved=$((moved + 1))
printf 'Moved: %s -> %s\n' "$name" "${target##*/}"
done
printf 'Completed: %s file(s) moved.\n' "$moved"
スクリプトのアクションの後に Quick Look を追加して、レポートを確認します。Appleは、アクション間でどのような内容が受け渡されるかを確認するため、テスト中に Quick Look を挿入することを特に推奨しています。Macのショートカットでアクションをテストするを参照してください。
レポートには、完了した移動についてのテキストが含まれます。これは移動した File オブジェクトの一覧ではありません。後でショートカットを拡張する場合、レポートの各行をファイル入力として扱わないでください。Appleの説明によると、アクションごとに受け入れるコンテンツの種類と生成するコンテンツの種類が決まっています。アクションの 情報を表示 を使うと、受け入れる入力と結果の出力を確認できます。Macのショートカットでアクションを接続するを参照してください。
この命名ルールで両方のコピーが残るのはなぜですか?
各移動元ファイルについて、スクリプトはまず移動先に元のパスが存在するか確認します。そのパスが使用済みなら、(2)、次に (3) を試し、未使用の候補が見つかるまで続けます。既存のディレクトリやシンボリックリンクも、名前が使用済みと見なされます。最終的な移動では、追加の上書き防止チェックとして mv -n を使います。これはAppleのmv(1) のドキュメントに沿ったものです。
| 移動元のファイル | 移動先にすでにあるファイル | 選ばれる移動先の名前 |
|---|---|---|
notes.txt |
同名のファイルなし | notes.txt |
notes.txt |
notes.txt |
notes (2).txt |
notes.txt |
notes.txt、notes (2).txt |
notes (3).txt |
readme |
readme |
readme (2) |
archive.tar.gz |
archive.tar.gz |
archive.tar (2).gz |
これらはプログラムの命名ルールであり、Finderが自動で付ける名前について述べたものではありません。分離されるのは最後の拡張子だけです。archive.tar (2).gz では .gz が末尾に残ります。すでに notes (2).txt という名前の移動元ファイルがある場合は、そのファイル名の本体が維持され、必要なら notes (2) (2).txt になります。スクリプトは、既存の数字をバージョン番号として解釈しません。
このプログラムではパスを引用符で囲み、テキスト形式のファイル一覧を分割する代わりに、シェルで展開されたファイル名を順に処理します。そのため、スペースを含む名前も1つの項目として扱われます。構文はBashを前提としているので、/bin/bash を選択したままにしてください。Appleのシェルスクリプトの基本では、シェルの種類による違いが重要な理由を説明しています。
例を実行して結果を確認する
ショートカットを1回実行し、Finderで両方のフォルダを確認します。サンプルの合成データでは、移動先に次のファイルがあるのが期待される結果です。
notes.txt。Existing synthetic notes.がそのまま含まれています。notes (2).txt。Earlier synthetic second copy.がそのまま含まれています。notes (3).txt。Incoming synthetic notes.が含まれています。readme.txt。Synthetic readme.が含まれています。
これで Incoming フォルダは空になり、レポートの最後には Completed: 2 file(s) moved. と表示されるはずです。ファイルを追加せずにショートカットを再度実行すると、移動件数はゼロと報告されます。これらは例における期待結果です。この記事の準備中に、macOSのショートカットワークフロー全体を実行したわけではありません。ファイル名を分割する式は、合成した名前を使って個別に確認しました。
ショートカットが停止した場合は、再実行する前に両方のフォルダを確認してください。ファイルは1つずつ移動されるため、後のファイルでエラーが発生しても、それ以前の移動は元に戻りません。スクリプトは、各コマンドが完了すればファイルが移動したと決めつけず、コマンドの実行後に移動元にファイルが残っているか確認します。
通常の移動に失敗した場合、Appleはファイルが開かれていないか、移動先に十分な空き容量があるかを確認するよう勧めています。Macで項目を移動またはコピーできない場合を参照してください。接尾辞を追加した結果、ファイル名が長すぎる場合も、このプログラムでは移動を完了できません。その場合、名前を切り詰めずに停止します。
ファイルの存在確認と移動は別々の操作です。このプログラムは、安定したフォルダを使い、手動で一度に1つずつ実行するローカルワークフローを想定しています。同時に変更が行われる場合に備えて、候補名を予約することはありません。この例のフォルダでは、同時実行や別のプロセスによる書き込みを避けてください。
競合が少数の場合はFinderを使う
少数のファイルを移動する場合は、移動前に移動元のファイル名を明示的に変更してください。合成データの例では、Collected を開き、Incoming/notes.txt を選択して Return を押し、notes (3).txt と入力して、もう一度 Return を押します。AppleはMacでファイル、フォルダ、ディスクの名前を変更するで、この名前変更の手順を説明しています。
次に、名前を変更したファイルを同じディスク上の Collected にドラッグします。Appleの説明によると、項目をフォルダにドラッグすると移動でき、Option を押しながらドラッグするとコピーでき、別のディスクへドラッグする際に Command を押すと移動できます。Macでフォルダ内のファイルを整理するを参照してください。別の名前の競合が発生した場合は、転送をキャンセルして、ほかの未使用の名前を選びます。
使い捨ての例がMacで期待どおりに動作したら、同じ条件にある別のローカルフォルダの組み合わせに合わせて、src_dir と dst_dir だけを変更してください。連番付きの名前を選ぶループと完了確認は、ひとまとまりのままにします。未使用の名前を選ぶことで移動先のコピーを両方残せます。また、結果を確認すれば移動元のファイルが実際に移動したかどうかがわかります。
参考資料
- macOS用ショートカットの紹介 — Apple Developer。
- mv(1) マニュアル原文 — Apple Open Source Distributions。
- Macでフォルダ内のファイルを整理する — Apple Support。
- Macでファイル名拡張子を表示または非表示にする — Apple Support。
- Macでカスタムショートカットを作成する — Apple Support。
- Macのショートカットでアクションをテストする — Apple Support。
- Macのショートカットでアクションを接続する — Apple Support。
- シェルスクリプトの基本 — Apple Developer。
- Macで項目を移動またはコピーできない場合 — Apple Support。
- Macでファイル、フォルダ、ディスクの名前を変更する — Apple Support。