Linux: salin dan buat direktori tujuan jika tidak ada

347

Saya ingin perintah (atau mungkin opsi untuk cp) yang membuat direktori tujuan jika tidak ada.

Contoh:

cp -? file /path/to/copy/file/to/is/very/deep/there
flybywire
sumber
5
Jawaban yang diterima salah (ada opsi untuk cp). Lihat jawaban kedua oleh Paul Whipp.
Ronenz
1
@Ronenz, saya setuju bahwa ada opsi yang layak disebutkan dalam GNU cp(seperti yang dijawab oleh Paul Whipp), tetapi ini tidak umum seperti yang diminta OP. Hal ini dapat menyalin a/foo/bar/fileke b/foo/bar/filetetapi tidak dapat menyalin fileke b/foo/bar/file. (Yaitu hanya berfungsi untuk membuat direktori induk yang sudah ada dalam kasus file pertama, tetapi tidak dapat membuat direktori sewenang-wenang)
Sudo Bash
Itu terlihat seperti permintaan fitur yang bagus untuk cp. Apakah ada kemungkinan kita bisa mendapatkan saklar baris perintah dalam waktu dekat?
Arnaud

Jawaban:

327
mkdir -p "$d" && cp file "$d"

(tidak ada opsi untuk cp).

Michael Krelin - hacker
sumber
62
Saya rasa Anda tidak perlu test -d: mkdir -pmasih berhasil walaupun direktori sudah ada.
ephemient
Ups, benar. Tapi kemudian, tes mungkin builtin bash (terutama jika ditulis sebagai [[-d "$ d"]]) dan mkdir tidak bisa ;-)
Michael Krelin - hacker
1
mkdiradalah builtin di BusyBox;)
ephemient
7
@holms, $dadalah variabel yang mungkin berisi direktori yang dimaksud. Maksud saya, jika Anda harus mengulanginya tiga kali, Anda cenderung menugaskannya ke variabel terlebih dahulu.
Michael Krelin - hacker
237

Jika kedua hal berikut ini benar:

  1. Anda menggunakan versi GNU dari cp (dan bukan, misalnya, versi Mac), dan
  2. Anda menyalin dari beberapa struktur direktori yang ada dan Anda hanya perlu membuatnya kembali

maka Anda dapat melakukan ini dengan --parentsbendera cp. Dari halaman info (dapat dilihat di http://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#cp-invocation atau dengan info cpatau man cp):

--parents
     Form the name of each destination file by appending to the target
     directory a slash and the specified name of the source file.  The
     last argument given to `cp' must be the name of an existing
     directory.  For example, the command:

          cp --parents a/b/c existing_dir

     copies the file `a/b/c' to `existing_dir/a/b/c', creating any
     missing intermediate directories.

Contoh:

/tmp $ mkdir foo
/tmp $ mkdir foo/foo
/tmp $ touch foo/foo/foo.txt
/tmp $ mkdir bar
/tmp $ cp --parents foo/foo/foo.txt bar
/tmp $ ls bar/foo/foo
foo.txt
Paul Whipp
sumber
4
Ini tidak berfungsi pada Mac OS X, jadi saya kira itu spesifik Linux.
olt
7
Saya akan mengatakan gnu-spesifik. Jika sudah, macportsAnda dapat menginstal coreutils dan gcp.
Michael Krelin - hacker
16
Sobat, linux memiliki segalanya
Ziggy
19
Saya merasa ini adalah jawaban untuk pertanyaan yang sedikit berbeda, meskipun itu adalah jawaban yang saya cari (dan karena itu yang saya pilih). Saya kira ini solusinya dengan asumsi Anda ingin direktori induk tujuan sama dengan direktori induk asal, yang mungkin merupakan kasus penggunaan yang menarik bagi kebanyakan orang.
David Winiecki
7
Ini mengasumsikan 'bar' direktori sudah ada, tetapi pertanyaan asli menyiratkan bagaimana membuat 'bar' secara otomatis ketika disediakan sebagai tujuan dan itu belum ada. Jawabannya diberikan oleh @ MichaelKrelin-hacker.
thdoan
69

Jawaban singkat

Untuk menyalin myfile.txtke /foo/bar/myfile.txt, gunakan:

mkdir -p /foo/bar && cp myfile.txt $_

Bagaimana cara kerjanya?

Ada beberapa komponen untuk ini, jadi saya akan membahas semua langkah demi langkah sintaksis.

The mkdir utilitas, sebagaimana ditentukan dalam standar POSIX , membuat direktori. The -pargumen, per dokumen, akan menyebabkan mkdir untuk

Membuat komponen pathname perantara yang hilang

artinya ketika memanggil mkdir -p /foo/bar, mkdir akan membuat /foo dan /foo/bar jika /foobelum ada. (Tanpa -p, itu malah akan menimbulkan kesalahan.

The &&Operator daftar, seperti yang didokumentasikan dalam standar POSIX (atau pengguna Bash jika Anda suka), memiliki efek yang cp myfile.txt $_hanya dijalankan jika mkdir -p /foo/barmengeksekusi berhasil. Ini berarti cpperintah tidak akan mencoba untuk mengeksekusi jika mkdirgagal karena salah satu dari banyak alasan itu mungkin gagal .

Akhirnya, $_kita cpmenganggap argumen kedua adalah "parameter khusus" yang berguna untuk menghindari pengulangan argumen panjang (seperti path file) tanpa harus menyimpannya dalam variabel. Per manual Bash , itu:

memperluas argumen terakhir ke perintah sebelumnya

Dalam hal ini, itulah yang /foo/barkami berikan mkdir. Jadi cpperintah diperluas ke cp myfile.txt /foo/bar, yang menyalin myfile.txtke /foo/bardirektori yang baru dibuat .

Perhatikan bahwa $_ini bukan bagian dari standar POSIX , sehingga secara teoritis varian Unix mungkin memiliki shell yang tidak mendukung konstruk ini. Namun, saya tidak tahu adanya kerang modern yang tidak mendukung $_; tentu Bash, Dash, dan zsh semua lakukan.


Catatan terakhir: perintah yang saya berikan di awal jawaban ini mengasumsikan bahwa nama direktori Anda tidak memiliki spasi. Jika Anda berurusan dengan nama dengan spasi, Anda harus mengutipnya sehingga kata-kata yang berbeda tidak diperlakukan sebagai argumen berbeda dengan mkdiratau cp. Jadi perintah Anda sebenarnya akan terlihat seperti:

mkdir -p "/my directory/name with/spaces" && cp "my filename with spaces.txt" "$_"
Mark Amery
sumber
16
Selain itu: Saya biasanya kecewa dengan kurangnya detail dalam pertanyaan tentang perintah shell Bash atau Unix di Stack Overflow, yang sering mengeluarkan satu-liner yang rumit dan sangat padat yang sulit dihapus jika Anda belum menjadi penyihir Unix. Saya sudah mencoba untuk mencoba ekstrim yang berlawanan di sini dan keduanya menjelaskan setiap detail sintaks dan tautan ke tempat yang tepat untuk menemukan dokumentasi tentang bagaimana hal ini bekerja. Saya harap ini membantu setidaknya beberapa orang. Juga, kredit yang jatuh tempo: Saya menandai pendekatan ini dari jawaban ini menjadi pertanyaan rangkap: stackoverflow.com/a/14085147/1709587
Mark Amery
Saya belum pernah melihat $ _ sebelumnya. apa yang dimaksud folder yang digunakan dalam perintah terakhir?
thebunnyrules
9
@thebunnyrules Tapi ... tapi ... ada "Bagaimana cara kerjanya?" bagian dalam jawaban saya yang secara harfiah berisi beberapa paragraf khusus yang ditujukan untuk pertanyaan yang baru saja Anda tanyakan. Rasanya diabaikan dan tidak dicintai. :(
Mark Amery
Maaf soal itu. Anda benar sekali. Saya sudah tahu semua hal lain dalam jawaban Anda, jadi saya segera memindai semua itu. Seharusnya saya membacanya lebih hati-hati.
thebunnyrules
Wow, Anda benar-benar memasukkan banyak pekerjaan ke dalam jawaban itu ... Terima kasih untuk itu. Sangat menyenangkan untuk belajar tentang: $ _. Saya bisa melihat diri saya menggunakannya sedikit mulai sekarang. Saya menulis naskah untuk mengotomatiskan seluruh proses dan memasang di utas ini.
Cobalah
39

Pertanyaan yang lama, tapi mungkin saya bisa mengusulkan solusi alternatif.

Anda dapat menggunakan installprogram ini untuk menyalin file Anda dan membuat jalur tujuan "on the fly".

install -D file /path/to/copy/file/to/is/very/deep/there/file

Namun ada beberapa aspek yang perlu dipertimbangkan:

  1. Anda harus menentukan juga nama file tujuan , bukan hanya jalur tujuan
  2. file tujuan akan dapat dieksekusi (setidaknya, sejauh yang saya lihat dari tes saya)

Anda dapat dengan mudah mengubah # 2 dengan menambahkan -mopsi untuk mengatur izin pada file tujuan (contoh: -m 664akan membuat file tujuan dengan izin rw-rw-r--, seperti halnya membuat file baru dengan touch).


Dan di sini adalah tautan tak tahu malu ke jawaban yang saya terinspirasi oleh =)

help_asap
sumber
Tidak benar-benar berfungsi untuk penggunaan saya tetapi trik keren! Saya akan mengingatnya.
thebunnyrules
perhatikan bahwa perintah ini membuat file tujuan dengan 755( rwxr-xr-x) izin, yang mungkin tidak diinginkan. Anda dapat menentukan sesuatu yang lain dengan -msaklar, tetapi saya tidak dapat menemukan cara untuk hanya menyimpan izin file :(
törzsmókus
19

Fungsi Shell yang melakukan apa yang Anda inginkan, menyebutnya salinan "mengubur" karena menggali lubang untuk tempat tinggal file:

bury_copy() { mkdir -p `dirname $2` && cp "$1" "$2"; }
Andy Ross
sumber
1
Anda harus memiliki tanda kutip di sekitar dirname $2juga
Tarnay Kálmán
4
@ Kalmi, untuk kutipan yang tepat Anda juga ingin mengutip $2sebagai argumen dirname. Seperti mkdir -p "$(dirname "$2")". Tanpa ini mengutip $2di cptidak berguna;)
Michael Krelin - hacker
3
Perhatikan bahwa jawaban ini menganggap $ 2 belum menjadi direktori target, jika tidak Anda hanya ingin "mkdir -p" $ 2 "&& cp" $ 1 "" $ 2 "sebagai badan fungsi
user467257
Saya pikir ini memiliki bug, seperti yang ditunjukkan oleh @ user467257. Saya tidak berpikir ini bisa diperbaiki, karena $ 2 ambigu antara direktori target dan file target dalam skenario ini. Saya telah memposting jawaban alternatif yang membahas hal ini.
Kaya
@ Rich, saya tidak setuju bahwa ini tidak dapat diperbaiki. Berikut ini berfungsi dengan baik untuk file dan direktori: mkdir -p dirname "$2"&& cp -r "$ 1" "$ 2"; Perhatikan bahwa backtick di sekitar dirname $2tidak muncul karena SO mem-parsing mereka sebagai penanda kode.
Yury
11

Inilah satu cara untuk melakukannya:

mkdir -p `dirname /path/to/copy/file/to/is/very/deep/there` \
   && cp -r file /path/to/copy/file/to/is/very/deep/there

dirnameakan memberi Anda induk dari direktori tujuan atau file. mkdir -p `dirname ...` kemudian akan membuat direktori itu memastikan bahwa ketika Anda memanggil cp -r direktori basis yang benar sudah ada.

Keuntungan over - orang tua ini adalah bahwa ia bekerja untuk kasus di mana elemen terakhir di jalur tujuan adalah nama file.

Dan itu akan bekerja pada OS X.

Jamie McCrindle
sumber
6

install -D file -m 644 -t /path/to/copy/file/to/is/very/deep/there

Spongman
sumber
1
sebenarnya, itu tidak sama sekali. itu mengharuskan Anda untuk melewatkan nama file dua kali (maka bendera '-t') dan itu menetapkan bit eksekusi pada file (maka contoh '-m'). seharusnya tidak menjadi jawaban teratas, karena itu tidak benar-benar menjawab pertanyaan - sedangkan saya menjawab.
Spongman
1
Ini berfungsi untuk satu file. Hanya memperhitungkan bahwa itu thereadalah file terakhir dan bukan subdirektori akhir.
kvantour
6

dengan segala hormat saya untuk jawaban di atas, saya lebih suka menggunakan rsync sebagai berikut:

$  rsync -a directory_name /path_where_to_inject_your_directory/

contoh:

$ rsync -a test /usr/local/lib/
marcdahan
sumber
Saya mencobanya dan menuju hasil ini: rsync -a bull / some / hoop / ti / doop / ploop HASIL rsync: mkdir "/ some / hoop / ti / doop / ploop" gagal: Tidak ada file atau direktori (2) kesalahan rsync : kesalahan dalam file IO (kode 11) di main.c (675) [Receiver = 3.1.2]
thebunnyrules
@thebunnyrules, Anda harus memeriksa lintasan dengan perintah pwd (lihat: [tautan] ( access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/… )) dan masukkan dengan benar ke dalam instruksi Anda
marcdahan
Jika saya memahami saran Anda dengan benar, Anda mengusulkan agar saya menentukan path lengkap ke sumber alias gunakan: "$ (pwd) / bull" di perintah saya alih-alih hanya nama file: bull? Masalahnya, kesalahannya adalah dengan rsync membuat tujuan tujuan, bukan sumber (bull). Menurut saya rsync menggunakan mkdir sederhana dan bukan mkdir -p.
thebunnyrules
1
Utilitas hebat, saya mungkin mulai menggunakan dalam skrip saya. Terima kasih telah merekomendasikannya.
thebunnyrules
1
rsync lebih dapat diprediksi daripada cp. Misalnya, jika saya ingin cp /from/somedir /to-dirmaka cp berperilaku berbeda jika /to-dirsudah ada: Jika /to-dirada, maka cpakan membuat /to-dir/some-dir, jika tidak, hanya konten /from/somedirditempatkan ke /to-dir. Namun, rsyncberperilaku sama dalam hal, yaitu, /to-dir/some-dirakan selalu dibuat.
JoeAC
5

Hanya untuk melanjutkan dan memberikan solusi kerja yang lengkap, dalam satu baris. Hati-hati jika Anda ingin mengganti nama file Anda, Anda harus memasukkan cara untuk memberikan path dir bersih ke mkdir. $ fdst dapat berupa file atau dir. Kode selanjutnya harus berfungsi dalam hal apa pun.

fsrc=/tmp/myfile.unk
fdst=/tmp/dir1/dir2/dir3/myfile.txt
mkdir -p $(dirname ${fdst}) && cp -p ${fsrc} ${fdst}

atau spesifik bash

fsrc=/tmp/myfile.unk
fdst=/tmp/dir1/dir2/dir3/myfile.txt
mkdir -p ${fdst%/*} && cp -p ${fsrc} ${fdst}
Kaalahaan
sumber
Terima kasih! Inilah yang saya butuhkan. Dalam kasus saya, saya tidak tahu apakah tujuan memiliki direktori atau tidak dan apakah direktori ada atau tidak sehingga kode ini memberi saya direktori induk yang kemudian saya dapat membuatnya dengan aman jika tidak ada
xbmono
4

Cukup tambahkan berikut ini di .bashrc Anda, tweak jika Anda perlu. Bekerja di Ubuntu.

mkcp() {
    test -d "$2" || mkdir -p "$2"
    cp -r "$1" "$2"
}

Misalnya Jika Anda ingin menyalin file 'test' ke direktori tujuan 'd' Gunakan,

mkcp test a/b/c/d

mkcp pertama-tama akan memeriksa apakah direktori tujuan ada atau tidak, jika tidak maka buatlah dan salin file sumber / direktori.

SD.
sumber
Seperti orang lain mengomentari jawaban lain, Anda tidak perlu menguji apakah direktori ada sebelum memanggil mkdir -p. Itu akan berhasil bahkan jika sudah ada.
bfontaine
3

Ini untukku

cp -vaR ./from ./to
Leroy Dunn
sumber
Setuju. Dalam man cp:-R If source_file designates a directory, cp copies the directory and the entire subtree connected at that point.
borracciaBlu
3

Saya menulis skrip dukungan untuk cp, yang disebut CP (huruf besar) yang dimaksudkan untuk melakukan hal ini. Script akan memeriksa kesalahan di jalur yang Anda masukkan (kecuali yang terakhir yang menjadi tujuan) dan jika semuanya baik-baik saja, ia akan melakukan langkah mkdir -p untuk membuat jalur tujuan sebelum memulai penyalinan. Pada titik ini utilitas cp reguler mengambil alih dan setiap sakelar yang Anda gunakan dengan CP (seperti -r, -p, -rpL akan disalurkan langsung ke cp). Sebelum Anda menggunakan skrip saya, ada beberapa hal yang perlu Anda pahami.

  • semua info di sini dapat diakses dengan melakukan CP --help. CP --membantu semua sakelar cp yang disertakan.
  • cp biasa tidak akan melakukan salinan jika tidak menemukan jalur tujuan. Anda tidak memiliki jaring pengaman untuk kesalahan ketik dengan CP. Tujuan Anda akan dibuat, jadi jika Anda salah mengeja tujuan Anda sebagai / usrr / share / ikon atau / usr / share / ikon dengan baik itulah yang akan dibuat.
  • regular cp cenderung memodelkan perilaku di jalur yang ada: cp / a / b / c / d akan bervariasi pada apakah d ada atau tidak. jika d adalah folder yang ada, cp akan menyalin b ke dalamnya, membuat / c / d / b. Jika d tidak ada, b akan disalin ke c dan diganti namanya menjadi d. Jika d ada tetapi merupakan file dan b adalah file, itu akan ditimpa oleh salinan b. Jika c tidak ada, cp tidak melakukan penyalinan dan keluar.

CP tidak memiliki kemewahan untuk mengambil isyarat dari jalur yang ada, sehingga harus memiliki beberapa pola perilaku yang sangat tegas. CP mengasumsikan bahwa item yang Anda salin dijatuhkan di jalur tujuan dan bukan tujuan itu sendiri (alias, salinan yang diubah namanya dari file sumber / folder). Berarti:

  • "CP / a / b / c / d" akan menghasilkan / c / d / b jika d adalah folder
  • "CP / a / b / c / b" akan menghasilkan / c / b / b jika b in / c / b adalah folder.
  • Jika b dan d adalah file: CP / a / b / c / d akan menghasilkan / c / d (di mana d adalah salinan b). Sama untuk CP / a / b / c / b dalam kondisi yang sama.

Perilaku CP default ini dapat diubah dengan sakelar "--rename". Dalam hal ini, diasumsikan demikian

  • "CP --rename / a / b / c / d" menyalin b ke / c dan mengganti nama salinan menjadi d.

Beberapa catatan penutup: Seperti dengan cp, CP dapat menyalin beberapa item sekaligus dengan jalur terakhir yang didaftarkan dianggap sebagai tujuan. Itu juga bisa menangani jalur dengan spasi selama Anda menggunakan tanda kutip.

CP akan memeriksa jalur yang Anda letakkan dan memastikan keberadaannya sebelum melakukan penyalinan. Dalam mode ketat (tersedia melalui saklar --strict), semua file / folder yang disalin harus ada atau tidak ada salinan yang terjadi. Dalam mode santai (--relaxed), penyalinan akan dilanjutkan jika setidaknya ada satu item yang Anda daftarkan. Mode santai adalah default, Anda dapat mengubah mode sementara melalui sakelar atau secara permanen dengan mengatur variabel easy_going di awal skrip.

Berikut cara menginstalnya:

Di terminal non-root, lakukan:

sudo echo > /usr/bin/CP; sudo chmod +x /usr/bin/CP; sudo touch /usr/bin/CP
gedit admin:///usr/bin/CP 

Di gedit, rekatkan utilitas CP dan simpan:

#!/bin/bash
#Regular cp works with the assumption that the destination path exists and if it doesn't, it will verify that it's parent directory does.

#eg: cp /a/b /c/d will give /c/d/b if folder path /c/d already exists but will give /c/d (where d is renamed copy of b) if /c/d doesn't exists but /c does.

#CP works differently, provided that d in /c/d isn't an existing file, it assumes that you're copying item into a folder path called /c/d and will create it if it doesn't exist. so CP /a/b /c/d will always give /c/d/b unless d is an existing file. If you put the --rename switch, it will assume that you're copying into /c and renaming the singl item you're copying from b to d at the destination. Again, if /c doesn't exist, it will be created. So CP --rename /a/b /c/d will give a /c/d and if there already a folder called /c/d, contents of b will be merged into d. 

#cp+ $source $destination
#mkdir -p /foo/bar && cp myfile "$_"

err=0 # error count
i=0 #item counter, doesn't include destination (starts at 1, ex. item1, item2 etc)
m=0 #cp switch counter (starts at 1, switch 1, switch2, etc)
n=1 # argument counter (aka the arguments inputed into script, those include both switches and items, aka: $1 $2 $3 $4 $5)
count_s=0
count_i=0
easy_going=true #determines how you deal with bad pathes in your copy, true will allow copy to continue provided one of the items being copied exists, false will exit script for one bad path. this setting can also be changed via the custom switches: --strict and --not-strict
verbal="-v"


  help="===============================================================================\
    \n         CREATIVE COPY SCRIPT (CP) -- written by thebunnyrules\
    \n===============================================================================\n
    \n This script (CP, note capital letters) is intended to supplement \
    \n your system's regular cp command (note uncapped letters). \n
    \n Script's function is to check if the destination path exists \
    \n before starting the copy. If it doesn't it will be created.\n    
    \n To make this happen, CP assumes that the item you're copying is \
    \n being dropped in the destination path and is not the destination\
    \n itself (aka, a renamed copy of the source file/folder). Meaning:\n 
    \n * \"CP /a/b /c/d\" will result in /c/d/b \
    \n * even if you write \"CP /a/b /c/b\", CP will create the path /a/b, \
    \n   resulting in /c/b/b. \n
    \n Of course, if /c/b or /c/d are existing files and /a/b is also a\
    \n file, the existing destination file will simply be overwritten. \
    \n This behavior can be changed with the \"--rename\" switch. In this\
    \n case, it's assumed that \"CP --rename /a/b /c/d\" is copying b into /c  \
    \n and renaming the copy to d.\n
    \n===============================================================================\
    \n        CP specific help: Switches and their Usages \
    \n===============================================================================\n
    \
    \n  --rename\tSee above. Ignored if copying more than one item. \n
    \n  --quiet\tCP is verbose by default. This quiets it.\n
    \n  --strict\tIf one+ of your files was not found, CP exits if\
    \n\t\tyou use --rename switch with multiple items, CP \
    \n\t\texits.\n
    \n  --relaxed\tIgnores bad paths unless they're all bad but warns\
    \n\t\tyou about them. Ignores in-appropriate rename switch\
    \n\t\twithout exiting. This is default behavior. You can \
    \n\t\tmake strict the default behavior by editing the \
    \n\t\tCP script and setting: \n
    \n\t\teasy_going=false.\n
    \n  --help-all\tShows help specific to cp (in addition to CP)."

cp_hlp="\n\nRegular cp command's switches will still work when using CP.\
    \nHere is the help out of the original cp command... \
    \n\n===============================================================================\
    \n          cp specific help: \
    \n===============================================================================\n"

outro1="\n******************************************************************************\
    \n******************************************************************************\
    \n******************************************************************************\
    \n        USE THIS SCRIPT WITH CARE, TYPOS WILL GIVE YOU PROBLEMS...\
    \n******************************************************************************\
    \n******************************* HIT q TO EXIT ********************************\
    \n******************************************************************************"


#count and classify arguments that were inputed into script, output help message if needed
while true; do
    eval input="\$$n"
    in_=${input::1}

    if [ -z "$input" -a $n = 1 ]; then input="--help"; fi 

    if [ "$input" = "-h" -o "$input" = "--help" -o "$input" = "-?" -o "$input" = "--help-all" ]; then
        if [ "$input" = "--help-all" ]; then 
            echo -e "$help"$cp_hlp > /tmp/cp.hlp 
            cp --help >> /tmp/cp.hlp
            echo -e "$outro1" >> /tmp/cp.hlp
            cat /tmp/cp.hlp|less
            cat /tmp/cp.hlp
            rm /tmp/cp.hlp
        else
            echo -e "$help" "$outro1"|less
            echo -e "$help" "$outro1"
        fi
        exit
    fi

    if [ -z "$input" ]; then
        count_i=$(expr $count_i - 1 ) # remember, last item is destination and it's not included in cound
        break 
    elif [ "$in_" = "-" ]; then
        count_s=$(expr $count_s + 1 )
    else
        count_i=$(expr $count_i + 1 )
    fi
    n=$(expr $n + 1)
done

#error condition: no items to copy or no destination
    if [ $count_i -lt 0 ]; then 
            echo "Error: You haven't listed any items for copying. Exiting." # you didn't put any items for copying
    elif [ $count_i -lt 1 ]; then
            echo "Error: Copying usually involves a destination. Exiting." # you put one item and no destination
    fi

#reset the counter and grab content of arguments, aka: switches and item paths
n=1
while true; do
        eval input="\$$n" #input=$1,$2,$3,etc...
        in_=${input::1} #first letter of $input

        if [ "$in_" = "-" ]; then
            if [ "$input" = "--rename" ]; then 
                rename=true #my custom switches
            elif [ "$input" = "--strict" ]; then 
                easy_going=false #exit script if even one of the non-destinations item is not found
            elif [ "$input" = "--relaxed" ]; then 
                easy_going=true #continue script if at least one of the non-destination items is found
            elif [ "$input" = "--quiet" ]; then 
                verbal=""
            else
                #m=$(expr $m + 1);eval switch$m="$input" #input is a switch, if it's not one of the above, assume it belongs to cp.
                switch_list="$switch_list \"$input\""
            fi                                  
        elif ! [ -z "$input" ]; then #if it's not a switch and input is not empty, it's a path
                i=$(expr $i + 1)
                if [ ! -f "$input" -a ! -d "$input" -a "$i" -le "$count_i" ]; then 
                    err=$(expr $err + 1 ); error_list="$error_list\npath does not exit: \"b\""
                else
                    if [ "$i" -le "$count_i" ]; then 
                        eval item$i="$input" 
                        item_list="$item_list \"$input\""
                    else
                        destination="$input" #destination is last items entered
                    fi
                fi
        else
            i=0
            m=0
            n=1                     
            break
        fi      
        n=$(expr $n + 1)
done

#error condition: some or all item(s) being copied don't exist. easy_going: continue if at least one item exists, warn about rest, not easy_going: exit.
#echo "err=$err count_i=$count_i"
if [ "$easy_going" != true -a $err -gt 0 -a $err != $count_i ]; then 
    echo "Some of the paths you entered are incorrect. Script is running in strict mode and will therefore exit."
    echo -e "Bad Paths: $err $error_list"
    exit
fi

if [ $err = $count_i ]; then
    echo "ALL THE PATHS you have entered are incorrect! Exiting."
    echo -e "Bad Paths: $err $error_list"
fi

#one item to one destination:
#------------------------------
#assumes that destination is folder, it does't exist, it will create it. (so copying /a/b/c/d/firefox to /e/f/firefox will result in /e/f/firefox/firefox
#if -rename switch is given, will assume that the top element of destination path is the new name for the the item being given.

#multi-item to single destination:
#------------------------------
#assumes destination is a folder, gives error if it exists and it's a file. -rename switch will be ignored.

#ERROR CONDITIONS: 
# - multiple items being sent to a destination and it's a file.
# - if -rename switch was given and multiple items are being copied, rename switch will be ignored (easy_going). if not easy_going, exit.
# - rename option but source is folder, destination is file, exit.
# - rename option but source is file and destination is folder. easy_going: option ignored.

if [ -f "$destination" ]; then
    if [ $count_i -gt 1 ]; then 
        echo "Error: You've selected a single file as a destination and are copying multiple items to it. Exiting."; exit
    elif [ -d "$item1" ]; then
        echo "Error: Your destination is a file but your source is a folder. Exiting."; exit
    fi
fi
if [ "$rename" = true ]; then
    if [ $count_i -gt 1 ]; then
        if [ $easy_going = true ]; then
            echo "Warning: you choose the rename option but are copying multiple items. Ignoring Rename option. Continuing."
        else
            echo "Error: you choose the rename option but are copying multiple items. Script running in strict mode. Exiting."; exit
        fi
    elif [ -d "$destination" -a -f "$item1" ]; then
        echo -n "Warning: you choose the rename option but source is a file and destination is a folder with the same name. "
        if [ $easy_going = true ]; then
            echo "Ignoring Rename option. Continuing."
        else
            echo "Script running in strict mode. Exiting."; exit
        fi
    else
        dest_jr=$(dirname "$destination")
        if [ -d "$destination" ]; then item_list="$item1/*";fi
        mkdir -p "$dest_jr"
    fi
else
    mkdir -p "$destination"
fi

eval cp $switch_list $verbal $item_list "$destination"

cp_err="$?"
if [ "$cp_err" != 0 ]; then 
    echo -e "Something went wrong with the copy operation. \nExit Status: $cp_err"
else 
    echo "Copy operation exited with no errors."
fi

exit
aturan kebun
sumber
Ini mungkin kerja keras, tapi cukup efisien, bekerja seperti yang diharapkan, terima kasih tuan yang baik.
Xedret
2

cp memiliki beberapa penggunaan:

$ cp --help
Usage: cp [OPTION]... [-T] SOURCE DEST
  or:  cp [OPTION]... SOURCE... DIRECTORY
  or:  cp [OPTION]... -t DIRECTORY SOURCE...
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.

@ AndyRoss menjawab bekerja untuk

cp SOURCE DEST

gaya cp, tetapi melakukan hal yang salah jika Anda menggunakan

cp SOURCE... DIRECTORY/

gaya cp.

Saya pikir "DEST" tidak jelas tanpa garis miring dalam penggunaan ini (yaitu di mana direktori target belum ada), yang mungkin mengapa cptidak pernah menambahkan opsi untuk ini.

Jadi, inilah versi saya dari fungsi ini yang memberlakukan garis miring pada direktori tujuan:

cp-p() {
  last=${@: -1}

  if [[ $# -ge 2 && "$last" == */ ]] ; then
    # cp SOURCE... DEST/
    mkdir -p "$last" && cp "$@"
  else
    echo "cp-p: (copy, creating parent dirs)"
    echo "cp-p: Usage: cp-p SOURCE... DEST/"
  fi
}
Kaya
sumber
2

Baru saja mengalami masalah yang sama. Pendekatan saya adalah hanya memasukkan file ke arsip seperti:

tar cf your_archive.tar file1 /path/to/file2 path/to/even/deeper/file3

tar secara otomatis menyimpan file dalam struktur yang sesuai dalam arsip. Jika Anda berlari

tar xf your_archive.tar

file diekstraksi ke dalam struktur direktori yang diinginkan.

Chris
sumber
1

Seperti yang disarankan di atas oleh help_asap dan spongeman, Anda dapat menggunakan perintah 'install' untuk menyalin file ke direktori yang ada atau membuat membuat direktori tujuan baru jika belum ada.

Opsi 1 install -D filename some/deep/directory/filename
menyalin file ke direktori baru atau yang sudah ada dan memberikan izin nama file standar 755

Opsi 2 install -D filename -m640 some/deep/directory/filename
sesuai Opsi 1 tetapi memberikan nama file 640 izin.

Opsi 3 install -D filename -m640 -t some/deep/directory/
sesuai Opsi 2 tetapi menargetkan nama file ke direktori target sehingga nama file tidak perlu ditulis dalam sumber dan target.

Opsi 4 install -D filena* -m640 -t some/deep/directory/
sesuai Opsi 3 tetapi menggunakan wildcard untuk banyak file.

Ini berfungsi dengan baik di Ubuntu dan menggabungkan dua langkah (pembuatan direktori kemudian menyalin file) menjadi satu langkah tunggal.

Menggali
sumber
1

Ini sangat terlambat tetapi mungkin membantu pemula di suatu tempat. Jika Anda perlu 'otomatis' membuat folder rsync harus menjadi teman terbaik Anda. rsync / path / ke / sourcefile / path / ke / tragetdir / thatdoestexist /

immanski njoro
sumber
0
rsync file /path/to/copy/file/to/is/very/deep/there

Ini mungkin berhasil, jika Anda memiliki jenis yang tepat rsync.

danuker
sumber
Ini tidak berfungsi untuk saya - saya mendapat "Tidak ada file atau direktori" di direktori tujuan
Rich
0

Salin dari sumber ke jalur yang tidak ada

mkdir p /destination && cp r /source/ $_

CATATAN: perintah ini menyalin semua file

cp –r untuk menyalin semua folder dan kontennya

$_ berfungsi sebagai tujuan yang dibuat dalam perintah terakhir

pengembang pengembang
sumber
0

Sederhana

cp -a * /path/to/dst/

harus melakukan trik.

Brad D.
sumber
Tidak. '/ Path / ke / dst /' harus ada sebelumnya.
Shital Shah
-1

Katakanlah Anda sedang melakukan sesuatu seperti

cp file1.txt A / B / C / D / file.txt

di mana A / B / C / D adalah direktori yang belum ada

Solusi yang mungkin adalah sebagai berikut

DIR=$(dirname A/B/C/D/file.txt)
# DIR= "A/B/C/D"
mkdir -p $DIR
cp file1.txt A/B/C/D/file.txt

berharap itu bisa membantu!

sdinesh94
sumber