Bagaimana cara menyalin folder secara rekursif dengan cara idempoten menggunakan cp?

46

Saat saya gunakan

cp -R inputFolder outputFolder

hasilnya tergantung pada konteks :

  1. jika outputFoldertidak ada, itu akan dibuat, dan jalur folder yang dikloning akan outputFolder.
  2. jika outputFolderada, maka klon yang dibuat akan menjadioutputFolder/inputFolder

Ini mengerikan , karena saya ingin membuat beberapa skrip instalasi, dan jika pengguna menjalankannya dua kali secara tidak sengaja, ia akan outputFoldermembuat yang pertama kali, maka pada proses kedua semua hal akan dibuat sekali lagi di Windows outputFolder/inputFolder.

  • Saya ingin selalu perilaku pertama: buat klon di sebelah yang asli (sebagai saudara kandung).
  • Saya ingin menggunakan cpuntuk menjadi portabel (mis. MINGW tidak rsyncmengirim)
  • Saya memeriksa cp -R --parentstetapi ini membuat ulang jalan sampai pohon direktori (jadi klon tidak akan outputFoldertetapi some/path/outputFolder)
  • --remove-destinationatau --updatedalam kasus 2 tidak mengubah apa pun, masih ada hal-hal yang disalinoutputFolder/inputFolder

Apakah ada cara untuk melakukan ini tanpa terlebih dahulu memeriksa keberadaan outputFolder(jika folder tidak ada maka ...) atau menggunakan rm -rf outputFolder?

Apa cara UNIX portabel yang disetujui untuk melakukan itu?

jakub.g
sumber
2
Beberapa opsi tersebut bukan POSIX, jadi yang Anda coba tidak terlalu portabel. Jika Anda bisa hidup dengan sedikit kekurangan portabilitas, mengapa tidak menggunakannya rsync?
muru
1
Jika Anda tidak perlu menggunakan cp, perpipaan di antara dua tarperintah adalah cara yang andal dan bagus untuk menyalin pohon.
R ..
@R .. bisakah Anda memberikan contoh? @muru Saya ingin ini berfungsi di MINGW yang belum rsyncdikirim.
jakub.g
Dengan GNU tar, tar -C input -cf - . | tar -C output -xf -berfungsi. -Cmengubah direktori kerja, tetapi ini bukan opsi standar, jadi jika itu tidak didukung, Anda perlu menjalankan dua ter di direktori kerja yang benar untuk memulai, misalnya. ( cd input && tar -cf - . ) | ( cd output && tar -xf - )Namun jika Anda berurusan dengan Windows, saya hanya akan menggunakan jawaban roaima.
R ..

Jawaban:

54

Gunakan ini sebagai gantinya:

cp -R inputFolder/. outputFolder

Ini bekerja persis dengan cara yang sama, katakanlah, cp -R aaa/bbb cccberfungsi: jika ccctidak ada maka itu dibuat sebagai salinan bbbdan isinya; tetapi jika cccsudah ada maka ccc/bbbdibuat sebagai salinan bbbdan isinya.

Untuk hampir semua contoh dari bbbini memberikan perilaku yang tidak diinginkan yang Anda catat dalam Pertanyaan Anda. Namun, dalam situasi khusus bbbini ., hanya , begitu aaa/bbbjuga sebenarnya aaa/., yang pada gilirannya benar-benar hanya aaadengan nama lain. Jadi kami memiliki dua skenario ini:

  1. ccc tidak ada:

    Perintah itu cp -R aaa/. cccberarti "membuat cccdan menyalin isi aaa/.ke ccc/., yaitu menyalin aaake ccc.

  2. ccc memang ada:

    Perintah itu cp -R aaa/. cccberarti "salin isi aaa/.ke ccc/., yaitu salin aaake ccc.

roaima
sumber
3
Huh, itu yang baru bagi saya, tetapi tampaknya berhasil! Apakah ini didokumentasikan di mana saja?
Ilmari Karonen
1
Saya telah menguji dan inputFolder/.bukannya inputFolderitu benar-benar bekerja untuk saya di Windows / Git Bash. (Namun tidak berfungsi hanya dengan trailing /, saya perlu juga titik setelah slash). Saya memberi +1 karena ini menarik, meskipun mungkin agak sulit untuk menggunakannya dalam kode publik :)
jakub.g
@IlmariJaronen tidak perlu didokumentasikan secara eksplisit. Ikuti penjelasannya dan Anda akan melihat bagaimana itu hanya "mengikuti aturan".
roaima
18

Jangan salin foldernya, salin isinya saja:

## Create the target directory. The -p suppresses error messages
## if the directory already exists
mkdir -p outputFolder

## Copy the contents recursively, this will not recreate the parent
cp -R inputfolder/* outputfolder/

Dengan cara ini Anda berdua memastikan bahwa direktori target dibuat pertama kali skrip berjalan dan menghindari masalah saat menjalankannya kedua kalinya.

Chris Down dengan sangat tepat menunjukkan bahwa dalam bash, ini akan melewatkan file yang namanya dimulai dengan a .. Untuk menghindari ini, Anda dapat menjalankan shopt -s dotglobsebelum menjalankan perintah di atas.

Baik -puntukmkdir dan -Runtukcp didefinisikan oleh POSIX jadi ini harus portabel.

terdon
sumber
6
Catatan: ini tidak akan menyalin file yang dimulai dengan .. Dalam bash, Anda dapat menggunakannya dotglobuntuk itu.
Chris Down
2
Catatan metode ini kemungkinan gagal jika jumlah entri direktori dalam folder input besar, karena ekspansi glob bisa melebihi panjang baris perintah maksimum.
Tim Cutts
11

Coba -Topsi untuk cp. Ini ada di GNU coreutils cpversi 8.22; mungkin tidak portabel di luar itu.

Tom Hunt
sumber
1
Ya, sepertinya inilah yang saya inginkan: cp -T( cp --no-target-directory). unix.stackexchange.com/questions/94831/... Sayangnya versi saya cpjauh lebih tua.
jakub.g
1
+1 Menggunakan ini hari ini saja. -uadalah pilihan yang bagus untuk membuatnya malas.
PSkocik
4

Anda juga bisa menggunakan rsync:

rsync -uav inputFolder/ outputFolder/

(perhatikan garis miring, terutama setelah yang pertama)

Ned64
sumber
0

Menurut pendapat saya, tidak ada yang lebih sederhana dan lebih portabel daripada rm -rf outputFolder. Jadi, saya selalu bertahan dengan itu. Saya mengerti bahwa pertanyaan Anda berbeda, tetapi saya pikir ini adalah praktik terbaik.

dashohoxha
sumber
Itu gagal jika ada izin atau kepemilikan khusus pada target yang perlu dijaga. Atau apakah itu symlink.
roaima
1
Pertanyaannya ingin hasilnya cpsama, baik ketika direktori tidak ada dan kapan ada. Jadi, apa yang kamu bicarakan?
dashohoxha
The cpperintah seperti yang diberikan oleh OP tidak menyimpan izin (atau cap waktu).
roaima
0

Anda dapat menggunakan -topsi cpperintah sebagai:

cp -R inputFolder -t outputFolder

sekarang jika folder target tidak ada itu akan menimbulkan kesalahan:

cp: failed to access ‘outputFolder’: No such file or directory

Catatan di atas perintah akan menyalin inputFolderbeserta isinya (dan bukan hanya isinya)

jika Anda ingin menyalin hanya isi inputFolderitu agak rumit (karena Anda harus berhati-hati saat menggunakan shell globbing saat menggunakan tanda bintang *)

cp -R  -t outputFolder/ -- inputFolder/*

sekarang jika folder target tidak ada itu akan menimbulkan kesalahan:

cp: failed to access ‘outputFolder’: No such file or directory

bekerja dengan cp (GNU coreutils) 8.23

Edward Torvalds
sumber