Bash: Salin file bernama secara rekursif, pertahankan struktur folder

103

Aku berharap:

cp -R src/prog.js images/icon.jpg /tmp/package

akan menghasilkan struktur simetris di arah tujuan:

/tmp
|
+-- package
    |
    +-- src
    |   |
    |   +-- prog.js
    |
    +-- images
        |
        +-- icon.jpg

tetapi sebaliknya, kedua file tersebut disalin ke / tmp / package. Salinan datar. (Ini di OSX).

Apakah ada fungsi bash sederhana yang dapat saya gunakan untuk menyalin semua file, termasuk file yang ditentukan oleh wildcard (mis. Src / *. Js) ke tempat yang seharusnya di dalam direktori tujuan. Agak seperti "untuk setiap file, jalankan mkdir -p $(dirname "$file"); cp "$file" $(dirname "$file")", tapi mungkin satu perintah.

Ini adalah utas yang relevan, yang menunjukkan bahwa itu tidak mungkin. Solusi penulis tidak begitu berguna bagi saya, karena saya hanya ingin memberikan daftar file, wildcard atau tidak, dan semuanya disalin ke direktori tujuan. IIRC MS-DOS xcopy melakukan ini, tetapi tampaknya tidak ada padanan untuk cp.

mahemoff
sumber

Jawaban:

154

Sudahkah Anda mencoba menggunakan opsi --parents? Saya tidak tahu apakah OS X mendukung itu, tetapi itu berfungsi di Linux.

cp --parents src/prog.js images/icon.jpg /tmp/package

Jika itu tidak berhasil di OS X, coba

rsync -R src/prog.js images/icon.jpg /tmp/package

sebagai aif disarankan.

ustun
sumber
4
Terima kasih. ternyata "cp --parents" tidak dimungkinkan di mac, tapi senang mengetahui bendera unixen lainnya. rsync -R adalah solusi portabel paling sederhana untuk masalah ini.
mahemoff
1
Saya menerima yang ini karena keanggunan / ingatannya, tetapi baru saja menemukan ini tidak menyalin seluruh direktori (setidaknya di OSX), sedangkan tar di bawah ini melakukannya.
mahemoff
cp --parentsadalah opsi ilegal di OSX (BSD cp), tetapi gcp(GNU cp) berfungsi dengan baik. Jika belum ada di sistem Anda, gunakan brew install coreutils. U akan memiliki banyak utilitas dengan awalan g.
kyb
@ mahemoff cp -R --parentsdan rsync -rRmenyalin file dan direktori secara relatif.
Vortico
22

Satu arah:

tar cf - <files> | (cd /dest; tar xf -)
Randy Proctor
sumber
oh, saya suka ini jauh lebih baik daripada jawaban saya.
EMPraptor
4
Anda juga dapat menggunakan -Copsi untuk melakukan chdir untuk Anda - tar cf - _files_ | tar -C /dest xf -atau sesuatu seperti itu.
D. Shawley
terima kasih, ini singkat, meskipun saya lebih suka rsync untuk kesederhanaan.
mahemoff
1
Bagus! Apakah ada yang tahu bagaimana mengubahnya menjadi perintah shell? Ini harus menerima input N, N-1 pertama adalah file yang akan disalin, dan yang terakhir adalah folder tujuan.
arod
@arod $ {! #} adalah parameter terakhir dan gunakan ini untuk mendapatkan argumen sebelumnya stackoverflow.com/questions/1215538/… . Jika Anda menulis perintah, tautkan ke intinya di sini.
mahemoff
18

Alternatifnya, jika Anda sudah tua, gunakan cpio:

cd /source;
find . -print | cpio -pvdmB /target

Jelas, Anda dapat memfilter daftar file sesuka hati Anda.

Opsi '-p' adalah untuk mode 'pass-through' (dibandingkan dengan '-i' untuk input atau '-o' untuk output). '-V' adalah verbose (daftar file saat mereka diproses). '-M' mempertahankan waktu modifikasi. '-B' berarti gunakan 'blok besar' (di mana blok besar berukuran 5120 byte, bukan 512 byte); mungkin itu tidak berpengaruh hari ini.

Jonathan Leffler
sumber
1
Lebih baik digunakan -print0dengan kombinasi --nullopsi sehingga tidak akan rusak dengan karakter khusus dan semacamnya:find . -print0 | cpio -pvdmB --null /target
haridsv
Sebut saja jadul, sebut saja portabel, sebut saja apa saja, tapi saya sangat percaya cpiountuk tugas ini. Saya setuju bahwa opsi -print0dan -nullharus digunakan, jika tidak, pada titik tertentu, seseorang akan memberi Anda beberapa folder dengan 'karakter khusus' (spasi, kemungkinan besar) dan sesuatu akan terjadi. Bukan berarti saya berbicara dari pengalaman pribadi, tetapi Anda mungkin mencoba mencadangkan banyak file dan hanya berakhir dengan setengah dari mereka dicadangkan karena ada ruang dalam nama file. (Oke, saya berbicara dari pengalaman pribadi.)
bballdave025
@ bballdave025: Anda hanya mengalami masalah dengan cpioseperti yang ditunjukkan jika nama file berisi baris baru - maka biasanya Anda akan mendapatkan pesan kesalahan tentang dua (atau lebih) nama file yang tidak ditemukan untuk setiap baris baru dalam nama file. (Terkadang, Anda mungkin mendapatkan lebih sedikit pesan - tetapi itu membutuhkan perhatian yang cukup besar dalam menyusun kasus uji.). Saat saya gunakan cpio, tidak ada --nullpilihan; opsi tanda hubung ganda bukan bagian dari notasi opsi SVR4, dan konsep -print0juga tidak ada find. Tapi itu sudah lama sekali (pertengahan 90-an misalnya. Sebelum Linux mencapai dominasi).
Jonathan Leffler
Terima kasih atas detailnya, @Jonathan_Leffler. Saya suka belajar banyak hal di sini. Sekarang, saya mencoba mengingat kesalahan rekursif-copy yang saya buat yang memberi saya masalah dengan spasi - ada banyak cara yang bisa saya lakukan untuk membuat kesalahan itu,
bballdave025
@ bballdave025— Satu kemungkinan digunakan xargsdalam campuran - itu terbagi di ruang kosong - kosong, tab, baris baru. OTOH, saya tidak yakin bagaimana atau mengapa Anda melakukan itu. cpioManul GNU pada mode keluaran jelas tentang satu nama file per baris. Panduan pengguna SVR4 (dicetak 1990) tidak jelas: cpio -o(mode salin keluar) membaca input standar untuk mendapatkan daftar nama jalur dan menyalin file tersebut ke output standar bersama dengan nama jalur dan informasi status. Oleh karena itu, ada kemungkinan bahwa itu merusak nama di spasi.
Jonathan Leffler
16

Opsi -R rsync akan melakukan apa yang Anda harapkan. Ini adalah mesin fotokopi file yang sangat kaya fitur. Sebagai contoh:

$ rsync -Rv src/prog.js images/icon.jpg /tmp/package/
images/
images/icon.jpg
src/
src/prog.js

sent 197 bytes  received 76 bytes  546.00 bytes/sec
total size is 0  speedup is 0.00

Hasil sampel:

$ find /tmp/package
/tmp/package
/tmp/package/images
/tmp/package/images/icon.jpg
/tmp/package/src
/tmp/package/src/prog.js
Ryan Bright
sumber
1

Mencoba...

for f in src/*.js; do cp $f /tmp/package/$f; done

jadi untuk apa yang Anda lakukan awalnya ...

for f in `echo "src/prog.js images/icon.jpg"`; do cp $f /tmp/package/$f; done

atau

v="src/prog.js images/icon.jpg"; for f in $v; do cp $f /tmp/package/$f; done
EMPraptor
sumber
Anda tidak perlu echoatau di $vsini. Juga metode ini akan gagal jika direktori yang bersangkutan tidak ada di tujuan.
Weijun Zhou