Linux: skopiuj i utwórz katalog docelowy, jeśli nie istnieje

348

Chcę polecenie (lub prawdopodobnie opcję cp), która tworzy katalog docelowy, jeśli nie istnieje.

Przykład:

cp -? file /path/to/copy/file/to/is/very/deep/there
Lecieć jak po sznurku
źródło
5
Zaakceptowana odpowiedź jest zła (istnieje taka opcja dla cp). Zobacz drugą odpowiedź Paula Whippa.
Ronenz
1
@Ronenz, zgadzam się, że w GNU istnieje opcja, o której warto wspomnieć cp(na co odpowiedział Paul Whipp), ale nie jest ona tak ogólna, jak wymagała PO. Może kopiować a/foo/bar/filedo, b/foo/bar/fileale nie może kopiować filedo b/foo/bar/file. (tzn. działa tylko w celu utworzenia katalogów nadrzędnych, które już istniały w przypadku pierwszego pliku, ale nie można utworzyć dowolnych katalogów)
Sudo Bash
To wygląda na ładną prośbę o funkcję dla CP. Czy jest jakaś szansa, że ​​w najbliższej przyszłości uda nam się uzyskać przełącznik linii poleceń?
Arnaud

Odpowiedzi:

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

(nie ma takiej opcji cp).

Michael Krelin - haker
źródło
62
Nie sądzę, że potrzebujesz test -d: mkdir -pnadal działa, nawet jeśli katalog już istnieje.
ephemient
ups, racja. Ale wtedy, test może być polecenie wbudowane bash (zwłaszcza jeśli zapisać jako [[-d "$ d"]]) i mkdir nie może ;-)
Michael Krelin - haker
1
mkdirjest wbudowany w BusyBox;)
ephemient
7
@holms, $djest zmienną, która prawdopodobnie zawiera dany katalog. Chodzi mi o to, że jeśli musisz powtórzyć trzy razy, prawdopodobnie najpierw przypisasz go do zmiennej.
Michael Krelin - haker
237

Jeśli oba poniższe warunki są prawdziwe:

  1. Używasz wersji GNU cp(a nie na przykład wersji Mac) i
  2. Kopiujesz z istniejącej struktury katalogów i po prostu potrzebujesz jej odtworzyć

następnie można to zrobić z --parentsflagą cp. Ze strony informacyjnej (można zobaczyć na stronie http://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#cp-invocation lub za pomocą info cplub 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.

Przykład:

/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
źródło
4
To nie działa w systemie Mac OS X, więc myślę, że jest to specyficzne dla systemu Linux.
ol
7
Powiedziałbym, że specyficzne dla GNU. Jeśli masz macports, możesz zainstalować coreutils i jego gcp.
Michael Krelin - haker
16
Człowieku, Linux ma wszystko
Ziggy
19
Wydaje mi się, że jest to odpowiedź na nieco inne pytanie, mimo że to była odpowiedź, której szukałem (i dlatego ta, którą głosowałem). Sądzę, że jest to rozwiązanie przy założeniu, że chcesz, aby docelowe katalogi nadrzędne były takie same jak katalogi nadrzędne pochodzenia, co jest prawdopodobnie przypadkiem użycia, w którym większość osób czytających to jest interesujące.
David Winiecki
7
Zakłada się, że „pasek” katalogu już istnieje, ale oryginalne pytanie sugeruje, jak automatycznie utworzyć „pasek”, gdy jest podany jako miejsce docelowe i nie istnieje. Odpowiedź na to pytanie zapewnia @ MichaelKrelin-hacker.
thdoan
69

Krótka odpowiedź

Aby skopiować myfile.txtdo /foo/bar/myfile.txt, użyj:

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

Jak to działa?

Składa się na to kilka elementów, więc krok po kroku omówię całą składnię.

Narzędzie mkdir , określone w standardzie POSIX , tworzy katalogi. -pArgument za docs, spowoduje mkdir do

Utwórz brakujące komponenty pośredniej nazwy ścieżki

co oznacza, że podczas rozmowy mkdir -p /foo/bar, mkdir tworzy /foo i /foo/bar jeśli /foojeszcze nie istnieje. (Bez -ptego spowoduje zgłoszenie błędu.

&&Operator lista, co zostało udokumentowane w standardzie POSIX (lub instrukcji Bash jeśli wolisz), ma ten skutek, że cp myfile.txt $_tylko jeśli zostanie wykonany mkdir -p /foo/barpomyślnie. Oznacza to, że cppolecenie nie spróbuje wykonać, jeśli mkdirzakończy się niepowodzeniem z jednego z wielu powodów, dla których może się nie powieść .

Wreszcie, $_drugi argument podajemy jako cp„specjalny parametr”, który może być przydatny, aby uniknąć powtarzania długich argumentów (takich jak ścieżki plików) bez konieczności przechowywania ich w zmiennej. Zgodnie z instrukcją Bash :

rozwija się do ostatniego argumentu do poprzedniego polecenia

W tym przypadku do /foo/bartego przeszliśmy mkdir. Tak więc cppolecenie rozwija się do cp myfile.txt /foo/bar, który kopiuje myfile.txtdo nowo utworzonego /foo/barkatalogu.

Zauważ, że nie$_ jest to część standardu POSIX , więc teoretycznie wariant Uniksa może mieć powłokę, która nie obsługuje tej konstrukcji. Jednak nie znam żadnych nowoczesnych powłok, które nie obsługują $_; Z pewnością Bash, Dash i Zsh.


Ostatnia uwaga: polecenie, które wydałem na początku tej odpowiedzi, zakłada, że ​​w nazwach katalogów nie ma spacji. Jeśli masz do czynienia ze spacjami, musisz je zacytować, aby różne słowa nie są traktowane jako różne argumenty do mkdirlub cp. Więc twoje polecenie wyglądałoby tak:

mkdir -p "/my directory/name with/spaces" && cp "my filename with spaces.txt" "$_"
Mark Amery
źródło
16
Poza tym: zazwyczaj jestem rozczarowany brakiem szczegółów w pytaniach dotyczących poleceń powłoki Bash lub Unixa na stosie przepełnienia stosu, które często wyrzucają skomplikowany i wyjątkowo gęsty jednowarstwowy program, który trudno jest usunąć, jeśli nie jesteś jeszcze czarodziejem uniksowym. Starałem się tu znaleźć skrajne przeciwieństwo i obaj wyjaśniam każdy szczegół składni oraz link do odpowiednich miejsc, aby znaleźć dokumentację na temat tego, jak to działa. Mam nadzieję, że to pomoże przynajmniej niektórym ludziom. Również kredyt, w którym należne: poderwałem to podejście od tej odpowiedzi na duplikat pytania: stackoverflow.com/a/14085147/1709587
Mark Amery
Nigdy wcześniej nie widziałem $ _. do czego to się odnosi? folder użyty w ostatnim poleceniu?
thebunnyrules
9
@ thebunnyrules Ale… ale… jest „jak to działa?” sekcja w mojej odpowiedzi, która dosłownie zawiera wiele akapitów poświęconych właśnie zadanemu pytaniu. Czuje się ignorowany i niekochany. :(
Mark Amery
Przepraszam za to. Masz całkowitą rację. Wiedziałem już wszystko w twojej odpowiedzi, więc po prostu szybko to przejrzałem. Powinienem był to przeczytać uważniej.
thebunnyrules
Wow, naprawdę włożyłeś dużo pracy w tę odpowiedź ... Dzięki za to. Miło było dowiedzieć się o: $ _. Odtąd widzę, jak z niego korzystam. Napisałem skrypt automatyzujący cały proces i umieściłem w tym wątku.
Spróbuj
39

Takie stare pytanie, ale może mogę zaproponować alternatywne rozwiązanie.

Możesz użyć installprogramu do skopiowania pliku i utworzenia ścieżki docelowej „w locie”.

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

Należy jednak wziąć pod uwagę kilka aspektów:

  1. musisz podać także nazwę pliku docelowego , a nie tylko ścieżkę docelową
  2. plik docelowy będzie wykonywalny (przynajmniej, o ile widziałem z moich testów)

Możesz łatwo zmienić numer 2, dodając -mopcję ustawiania uprawnień do pliku docelowego (przykład: -m 664utworzy plik docelowy z uprawnieniami rw-rw-r--, podobnie jak tworzenie nowego pliku za pomocą touch).


A oto bezwstydny link do odpowiedzi, którą zainspirowałam =)

help_asap
źródło
Naprawdę nie działało na mój użytek, ale fajna sztuczka! Będę pamiętać.
thebunnyrules
zauważ, że to polecenie tworzy pliki docelowe z uprawnieniami 755( rwxr-xr-x), co prawdopodobnie nie jest pożądane. możesz określić coś innego za pomocą -mprzełącznika, ale nie mogłem znaleźć sposobu na zachowanie uprawnień do plików :(
törzsmókus
19

Funkcja powłoki, która robi to, co chcesz, nazywając ją kopią „zakopaną”, ponieważ wykopuje otwór, w którym znajduje się plik:

bury_copy() { mkdir -p `dirname $2` && cp "$1" "$2"; }
Andy Ross
źródło
1
Trzeba było w cudzysłowie dirname $2zbyt
Tarnay Kálmán
4
@Kalmi, aby uzyskać prawidłową wycenę, chciałbyś również podać ją $2jako argument dirname. Jak mkdir -p "$(dirname "$2")". Bez tego powołując $2się cpma sensu;)
Michael Krelin - haker
3
Zauważ, że ta odpowiedź zakłada, że ​​$ 2 nie jest już katalogiem docelowym, w przeciwnym razie chcesz po prostu „mkdir -p” $ 2 ”&& cp„ $ 1 ”„ $ 2 ”jako ciało funkcji
użytkownik467257
Myślę, że ma to błąd, jak wskazuje @ user467257. Nie sądzę, że można to naprawić, ponieważ 2 USD jest niejednoznaczne między docelowym katalogiem a plikiem docelowym w tym scenariuszu. Zamieściłem alternatywną odpowiedź, która rozwiązuje ten problem.
Bogaty
@Rich, nie zgadzam się, że nie można tego naprawić. Poniższe działa dobrze zarówno dla plików, jak i katalogów: mkdir -p dirname "$2"&& cp -r "$ 1" "$ 2"; Zauważ, że backticks wokół dirname $2się nie pojawiają, ponieważ SO analizuje je jako znaczniki kodu.
Yury
11

Oto jeden ze sposobów, aby to zrobić:

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

dirnameda ci rodzic katalogu docelowego lub pliku docelowego. mkdir -p `nazwa_katalogu ... 'utworzy ten katalog, upewniając się, że po wywołaniu cp -r znajduje się właściwy katalog podstawowy.

Zaletą tego nad-rodziców jest to, że działa w przypadku, gdy ostatnim elementem ścieżki docelowej jest nazwa pliku.

I będzie działać na OS X.

Jamie McCrindle
źródło
6

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

Gąbczasty
źródło
1
właściwie to wcale nie to samo. jego wymaga dwukrotnego przekazania nazwy pliku (stąd flaga „-t”) i ustawienia bitów wykonania w pliku (stąd przykład „-m”). jego nie powinna być najlepsza odpowiedź, ponieważ tak naprawdę nie odpowiada na pytanie - podczas gdy moja tak.
Spongman,
1
Działa to dla jednego pliku. Weź pod uwagę, że therejest to końcowy plik, a nie ostatni podkatalog.
kvantour
6

Z całym szacunkiem dla powyższych odpowiedzi wolę używać rsync w następujący sposób:

$  rsync -a directory_name /path_where_to_inject_your_directory/

przykład:

$ rsync -a test /usr/local/lib/
marcdahan
źródło
Spróbowałem i sprawdziłem ten wynik: rsync -a bull / some / hoop / ti / doop / ploop WYNIK rsync: mkdir "/ some / hoop / ti / doop / ploop" nie powiodło się: Brak takiego pliku lub katalogu (2) błąd rsync : błąd w pliku IO (kod 11) w main.c (675) [Receiver = 3.1.2]
thebunnyrules
@ thebunnyrules musisz sprawdzić ścieżkę za pomocą polecenia pwd (spójrz na: [link] ( access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/… )) i włóż poprawnie do instrukcji
marcdahan
Jeśli dobrze rozumiem twoją sugestię, proponuję podać pełną ścieżkę do źródła, czyli aka: „$ (pwd) / bull” w moim poleceniu zamiast samej nazwy pliku: bull? Rzecz w tym, że błąd polega na tym, że rsync tworzy katalog docelowy, a nie źródłowy (byk). Wydaje mi się, że rsync używa prostego mkdir, a nie mkdir -p.
thebunnyrules
1
Nawiasem mówiąc, świetne narzędzie, mogę zacząć używać w swoich skryptach. Dziękujemy za polecenie.
thebunnyrules
1
rsync jest bardziej przewidywalny niż cp. Na przykład, jeśli chcę, aby cp /from/somedir /to-dircp zachowywał się inaczej, jeśli /to-dirjuż istnieje: Jeśli /to-diristnieje, to cputworzy /to-dir/some-dir, w przeciwnym razie /from/somedirzostanie umieszczona tylko zawartość /to-dir. Jednak rsynczachowuje się tak samo w przypadku, czyli /to-dir/some-dirbędzie zawsze zostać utworzony.
JoeAC
5

Wystarczy wznowić i dać kompletne rozwiązanie robocze, w jednej linii. Zachowaj ostrożność, jeśli chcesz zmienić nazwę pliku, powinieneś podać sposób na zapewnienie czystej ścieżki katalogu mkdir. $ fdst może być plikiem lub reż. W każdym razie następny kod powinien działać.

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

lub specyficzne dla bash

fsrc=/tmp/myfile.unk
fdst=/tmp/dir1/dir2/dir3/myfile.txt
mkdir -p ${fdst%/*} && cp -p ${fsrc} ${fdst}
Kaalahaan
źródło
Dzięki! Właśnie tego potrzebowałem. W moim przypadku nie wiem, czy miejsce docelowe ma katalog, czy nie i czy katalog istnieje, więc ten kod daje mi katalog nadrzędny, który następnie mogę bezpiecznie utworzyć, jeśli nie istnieje
xbmono
4

Po prostu dodaj następujące elementy do .bashrc, w razie potrzeby popraw je. Działa w Ubuntu.

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

Np. Jeśli chcesz skopiować plik „testowy” do katalogu docelowego „d” Użyj,

mkcp test a/b/c/d

mkcp najpierw sprawdzi, czy katalog docelowy istnieje, czy nie, a następnie utworzy go i skopiuje plik źródłowy / katalog.

SD.
źródło
Gdy inni skomentowali drugą odpowiedź, nie musisz sprawdzać, czy katalog istnieje przed wywołaniem mkdir -p. Odniesie sukces, nawet jeśli już istnieje.
bfontaine
3

To mi wystarcza

cp -vaR ./from ./to
Leroy Dunn
źródło
Zgodzić się. W man cp:-R If source_file designates a directory, cp copies the directory and the entire subtree connected at that point.
borracciaBlu
3

Napisałem skrypt wsparcia dla cp, zwany CP (uwaga wielkimi literami), który jest przeznaczony właśnie do tego. Skrypt sprawdzi błędy w ścieżce, którą umieściłeś (z wyjątkiem ostatniego, który jest miejscem docelowym), a jeśli wszystko będzie dobrze, wykona krok mkdir -p, aby utworzyć ścieżkę docelową przed rozpoczęciem kopiowania. W tym momencie zwykłe narzędzie cp przejmuje kontrolę i wszelkie przełączniki używane z CP (jak -r, -p, -rpL są przesyłane bezpośrednio do cp). Zanim użyjesz mojego skryptu, musisz zrozumieć kilka rzeczy.

  • wszystkie informacje tutaj można uzyskać wykonując CP --help. CP - help-all zawiera przełączniki cp.
  • zwykły cp nie zrobi kopii, jeśli nie znajdzie ścieżki docelowej. Nie masz takiej siatki bezpieczeństwa dla literówek z CP. Miejsce docelowe zostanie utworzone, więc jeśli źle przeliterujesz miejsce docelowe jako / usrr / share / icons lub / usr / share / icon, to właśnie zostanie utworzone.
  • zwykłe cp ma tendencję do modelowania swojego zachowania na istniejącej ścieżce: cp / a / b / c / d będzie się różnić w zależności od tego, czy d istnieje, czy nie. jeśli d jest istniejącym folderem, cp skopiuje do niego b, tworząc / c / d / b. Jeśli d nie istnieje, b zostanie skopiowane do c i przemianowane na d. Jeśli d istnieje, ale jest plikiem, a b jest plikiem, zostanie zastąpione przez kopię b. Jeśli c nie istnieje, cp nie wykonuje kopii i kończy działanie.

CP nie ma luksusu czerpania wskazówek z istniejących ścieżek, więc musi mieć bardzo twarde wzorce zachowań. CP zakłada, że ​​element, który kopiujesz, jest upuszczany na ścieżkę docelową, a nie sam cel docelowy (inaczej kopia pliku źródłowego / folderu o zmienionej nazwie). Znaczenie:

  • „CP / a / b / c / d” spowoduje utworzenie / c / d / b, jeśli d jest folderem
  • „CP / a / b / c / b” spowoduje utworzenie / c / b / b, jeśli b w / c / b jest folderem.
  • Jeśli oba b i d są plikami: CP / a / b / c / d spowoduje utworzenie / c / d (gdzie d jest kopią b). To samo dotyczy CP / a / b / c / b w tych samych okolicznościach.

To domyślne zachowanie CP można zmienić za pomocą przełącznika „--rename”. W tym przypadku zakłada się, że

  • „CP - nazwa / a / b / c / d” kopiuje b do / c i zmienia nazwę kopii na d.

Kilka uwag na zakończenie: Podobnie jak w przypadku CP, CP może kopiować wiele przedmiotów na raz, przy założeniu, że ostatnia ścieżka jest na liście docelowej. Może także obsługiwać ścieżki ze spacjami, o ile używasz cudzysłowów.

CP sprawdzi ścieżki, które wstawiłeś i upewni się, że istnieją przed wykonaniem kopii. W trybie ścisłym (dostępnym za pomocą przełącznika --strict) wszystkie kopiowane pliki / foldery muszą istnieć lub kopiowanie nie ma miejsca. W trybie zrelaksowanym (--relaxed) kopiowanie będzie kontynuowane, jeśli przynajmniej jeden z wymienionych elementów istnieje. Tryb zrelaksowany jest domyślny, można go zmienić tymczasowo za pomocą przełączników lub na stałe, ustawiając zmienną easy_going na początku skryptu.

Oto jak go zainstalować:

W terminalu innym niż root wykonaj:

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

W gedit wklej narzędzie CP i zapisz:

#!/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
thebunnyrules
źródło
To może przesada, ale całkiem wydajne, działa zgodnie z oczekiwaniami, dziękuję dobry panie.
Xedret
2

cp ma wiele zastosowań:

$ 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.

@ Odpowiedź AndyRossa działa dla

cp SOURCE DEST

styl cp, ale robi coś złego, jeśli używasz

cp SOURCE... DIRECTORY/

styl cp.

Myślę, że „DEST” jest niejednoznaczny bez końcowego ukośnika w tym użyciu (tj. Gdzie katalog docelowy jeszcze nie istnieje), i być może dlatego cpnigdy nie dodał do tego opcji.

Oto moja wersja tej funkcji, która wymusza ukośnik końcowy w katalogu docelowym:

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
}
Bogaty
źródło
2

Właśnie miałem ten sam problem. Moje podejście polegało na tarowaniu plików do archiwum w następujący sposób:

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

tar automatycznie przechowuje pliki w odpowiedniej strukturze w archiwum. Jeśli uciekniesz

tar xf your_archive.tar

pliki są wyodrębniane do żądanej struktury katalogów.

Chris
źródło
1

Jak sugerują wyżej help_asap i spongeman, możesz użyć polecenia „install”, aby skopiować pliki do istniejących katalogów lub utworzyć nowe katalogi docelowe, jeśli jeszcze nie istnieją.

Opcja 1 install -D filename some/deep/directory/filename
kopiuje plik do nowego lub istniejącego katalogu i daje domyślne uprawnienia do nazwy pliku 755

Opcja 2 install -D filename -m640 some/deep/directory/filename
zgodnie z Opcją 1, ale daje uprawnienia do nazwy pliku 640.

Opcja 3 install -D filename -m640 -t some/deep/directory/
zgodnie z Opcją 2, ale wskazuje nazwę pliku w katalogu docelowym, więc nazwa pliku nie musi być zapisywana zarówno w źródle, jak i celu.

Opcja 4 install -D filena* -m640 -t some/deep/directory/
zgodnie z Opcją 3, ale używa symboli wieloznacznych dla wielu plików.

Działa ładnie w Ubuntu i łączy dwa kroki (tworzenie katalogu, a następnie kopiowanie plików) w jednym kroku.

Kopać
źródło
1

Jest bardzo późno, ale może pomóc gdzieś nowicjuszowi. Jeśli potrzebujesz „automatycznie” tworzyć foldery, najlepszym przyjacielem powinien być rsync. rsync / path / to / sourcefile / path / to / tragetdir / thatdoestexist /

immanski njoro
źródło
0
rsync file /path/to/copy/file/to/is/very/deep/there

To może zadziałać, jeśli masz odpowiedni rodzaj rsync.

Danuker
źródło
To nie działa dla mnie - mam „Brak takiego pliku lub katalogu” w katalogu docelowym
Rich
0

Skopiuj ze źródła do nieistniejącej ścieżki

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

UWAGA: to polecenie kopiuje wszystkie pliki

cp –r do kopiowania wszystkich folderów i ich zawartości

$_ działa jako miejsce docelowe utworzone w ostatnim poleceniu

deweloperprashant
źródło
0

Prosty

cp -a * /path/to/dst/

powinien załatwić sprawę.

Brad D.
źródło
Tak nie jest. „/ Path / to / dst /” musi istnieć wcześniej.
Shital Shah
-1

Powiedzmy, że robisz coś takiego

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

gdzie A / B / C / D to katalogi, które jeszcze nie istnieją

Możliwe rozwiązanie jest następujące

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

mam nadzieję, że to pomaga!

sdinesh94
źródło