Linux: copiez et créez le répertoire de destination s'il n'existe pas

348

Je veux une commande (ou probablement une option pour cp) qui crée le répertoire de destination s'il n'existe pas.

Exemple:

cp -? file /path/to/copy/file/to/is/very/deep/there
fil volant
la source
5
La réponse acceptée est fausse (il existe une telle option pour cp). Voir la deuxième réponse de Paul Whipp.
Ronenz
1
@Ronenz, je suis d'accord qu'il existe une option qui mérite d'être mentionnée dans GNU cp(comme l'a répondu Paul Whipp), mais elle n'est pas aussi générale que l'OP demandé. Il peut copier a/foo/bar/filevers b/foo/bar/filemais il ne peut pas copier filevers b/foo/bar/file. (c'est-à-dire que cela ne fonctionne que pour créer des répertoires parents qui existaient déjà dans le cas du premier fichier, mais il ne peut pas créer de répertoires arbitraires)
Sudo Bash
Cela ressemble à une belle demande de fonctionnalité pour cp. Y a-t-il une chance que nous puissions obtenir un tel commutateur de ligne de commande dans un avenir proche?
Arnaud

Réponses:

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

(il n'y a pas une telle option pour cp).

Michael Krelin - pirate
la source
62
Je ne pense pas que vous ayez besoin de test -d: mkdir -préussit toujours même si le répertoire existe déjà.
éphémère
oups. Mais alors, test peut être un bash intégré (surtout s'il est écrit comme [[-d "$ d"]]]) et mkdir ne peut pas ;-)
Michael Krelin - pirate
1
mkdirest une fonction intégrée dans BusyBox;)
éphémère
7
@holms, $dest une variable qui contient vraisemblablement le répertoire en question. Je veux dire, si vous devez le répéter trois fois, vous êtes susceptible de l'attribuer à la variable en premier.
Michael Krelin - hacker
237

Si les deux conditions suivantes sont vraies:

  1. Vous utilisez la version GNU de cp(et non, par exemple, la version Mac), et
  2. Vous copiez à partir d'une structure de répertoire existante et vous avez juste besoin de la recréer

alors vous pouvez le faire avec le --parentsdrapeau de cp. Depuis la page d'informations (consultable sur http://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#cp-invocation ou avec info cpou 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.

Exemple:

/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
la source
4
Cela ne fonctionne pas sur Mac OS X, donc je suppose que c'est spécifique à Linux.
olt
7
Je dirais spécifique au gnu. Si vous en avez, macportsvous pouvez installer coreutils et son gcp.
Michael Krelin - hacker
16
Man, Linux a tout
Ziggy
19
J'ai l'impression que c'est la réponse à une question légèrement différente, même si c'est la réponse que je cherchais (et donc celle que j'ai surévaluée). Je suppose que c'est la solution en supposant que vous vouliez que les répertoires parents de destination soient les mêmes que les répertoires parents d'origine, ce qui est probablement le cas d'utilisation dans lequel la plupart des gens lisent ceci sont intéressants.
David Winiecki
7
Cela suppose que le répertoire «bar» existe déjà, mais la question d'origine implique comment créer automatiquement «bar» lorsqu'il est fourni comme destination et qu'il n'existe pas déjà. La réponse à cela est fournie par @ MichaelKrelin-hacker.
thdoan
69

Réponse courte

Pour copier myfile.txtvers /foo/bar/myfile.txt, utilisez:

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

Comment cela marche-t-il?

Il y a quelques composants à cela, donc je vais couvrir toute la syntaxe étape par étape.

L' utilitaire mkdir , comme spécifié dans la norme POSIX , crée des répertoires. L' -pargument par la documentation, fera mkdir à

Créer tous les composants de chemin d'accès intermédiaires manquants

ce qui signifie que lors de l'appel mkdir -p /foo/bar, mkdir créera /foo et /foo/bar s'il /foon'existe pas déjà. (Sans -p, cela générera une erreur.

L' &&opérateur de liste, tel que documenté dans la norme POSIX (ou le manuel Bash si vous préférez), a l'effet qui cp myfile.txt $_n'est exécuté que s'il mkdir -p /foo/bars'exécute avec succès. Cela signifie que la cpcommande n'essaiera pas de s'exécuter en cas d' mkdiréchec pour l' une des nombreuses raisons pour lesquelles elle pourrait échouer .

Enfin, le $_second argument que nous passons cpest un "paramètre spécial" qui peut être pratique pour éviter de répéter de longs arguments (comme des chemins de fichier) sans avoir à les stocker dans une variable. Selon le manuel de Bash , il:

se développe jusqu'au dernier argument de la commande précédente

Dans ce cas, c'est le /foo/barnous sommes passés à mkdir. Ainsi, la cpcommande se développe vers cp myfile.txt /foo/bar, qui copie myfile.txtdans le /foo/barrépertoire nouvellement créé .

Notez que cela ne$_ fait pas partie de la norme POSIX , donc théoriquement une variante Unix pourrait avoir un shell qui ne prend pas en charge cette construction. Cependant, je ne connais aucun obus moderne qui ne supporte pas $_; certainement Bash, Dash et zsh le font tous.


Une dernière note: la commande que j'ai donnée au début de cette réponse suppose que vos noms de répertoires n'ont pas d'espaces. Si vous avez affaire à des noms avec des espaces, vous devrez les citer entre eux afin que les différents mots ne sont pas traités comme des arguments différents de mkdirou cp. Donc, votre commande ressemblerait en fait à:

mkdir -p "/my directory/name with/spaces" && cp "my filename with spaces.txt" "$_"
Mark Amery
la source
16
En plus: Je suis généralement déçu par le manque de détails dans les questions sur les commandes shell Bash ou Unix sur Stack Overflow, qui jettent souvent une ligne compliquée et extrêmement dense qui est difficile à décocher si vous n'êtes pas déjà un assistant Unix. J'ai essayé d'aller à l'extrême opposé ici et d'expliquer tous les détails de la syntaxe et de créer un lien vers les endroits appropriés pour trouver de la documentation sur le fonctionnement de ces éléments. J'espère que cela aide au moins certaines personnes. En outre, le crédit est dû: j'ai annulé cette approche à partir de cette réponse à une question en double: stackoverflow.com/a/14085147/1709587
Mark Amery
Je n'ai jamais vu $ _ auparavant. à quoi cela fait-il référence? le dossier utilisé dans la dernière commande?
thebunnyrules
9
@thebunnyrules Mais ... mais ... il y a un "Comment ça marche?" section de ma réponse qui contient littéralement plusieurs paragraphes spécifiquement consacrés à la question que vous venez de poser. Il se sent ignoré et mal aimé. :(
Mark Amery
Désolé pour ça. Vous avez absolument raison. Je savais déjà tout le reste dans votre réponse, je l'ai donc rapidement parcourue. J'aurais dû le lire plus attentivement.
thebunnyrules
Wow, vous avez vraiment mis beaucoup de travail dans cette réponse ... Merci pour cela. C'était agréable d'en savoir plus sur: $ _. Je peux me voir l'utiliser un peu à partir de maintenant. J'ai écrit un script pour automatiser l'ensemble du processus et mis en place dans ce fil. Essayez-le, vous aimerez peut-être: stackoverflow.com/questions/1529946/…
thebunnyrules
39

Une telle vieille question, mais je peux peut-être proposer une solution alternative.

Vous pouvez utiliser le installprogramme pour copier votre fichier et créer le chemin de destination "à la volée".

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

Il y a cependant certains aspects à prendre en considération:

  1. vous devez également spécifier le nom du fichier de destination , pas seulement le chemin de destination
  2. le fichier de destination sera exécutable (au moins, d'après ce que j'ai vu de mes tests)

Vous pouvez facilement modifier le # 2 en ajoutant l' -moption pour définir des autorisations sur le fichier de destination (exemple: -m 664créera le fichier de destination avec des autorisations rw-rw-r--, tout comme la création d'un nouveau fichier avec touch).


Et ici, c'est le lien sans vergogne vers la réponse qui m'a inspiré =)

help_asap
la source
N'a pas vraiment fonctionné pour mon utilisation mais astuce cool! Je m'en souviendrai.
thebunnyrules
notez que cette commande crée les fichiers de destination avec les autorisations 755( rwxr-xr-x), ce qui n'est probablement pas souhaité. vous pouvez spécifier autre chose avec le -mcommutateur, mais je n'ai pas pu trouver un moyen de simplement conserver les autorisations sur les fichiers :(
törzsmókus
19

Fonction Shell qui fait ce que vous voulez, en l'appelant une copie "enterrée" car elle creuse un trou dans lequel le fichier doit vivre:

bury_copy() { mkdir -p `dirname $2` && cp "$1" "$2"; }
Andy Ross
la source
1
Vous devriez avoir des guillemets autour du dirname $2trop
Tarnay Kálmán
4
@ Kalmi, pour une citation correcte, vous voudrez également citer $2comme argument dirname. Comme mkdir -p "$(dirname "$2")". Sans cette citation $2en cpest inutile;)
Michael Krelin - hacker
3
Notez que cette réponse suppose que $ 2 n'est pas déjà le répertoire cible, sinon vous voudriez simplement "mkdir -p" $ 2 "&& cp" $ 1 "" $ 2 "comme corps de fonction
user467257
Je pense que cela a un bug, comme le souligne @ user467257. Je ne pense pas que ce soit réparable, car $ 2 est ambigu entre le répertoire cible et le fichier cible dans ce scénario. J'ai publié une réponse alternative qui résout ce problème.
Rich
@ Rich, je ne suis pas d'accord que ce n'est pas réparable. Ce qui suit fonctionne très bien pour les fichiers et les répertoires: mkdir -p dirname "$2"&& cp -r "$ 1" "$ 2"; Notez que les backticks autour dirname $2ne s'affichent pas car SO les analyse en tant que marqueurs de code.
Yury
11

Voici une façon de procéder:

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

dirnamevous donnera le parent du répertoire ou du fichier de destination. mkdir -p `dirname ...` créera alors ce répertoire en s'assurant que lorsque vous appelez cp -r le répertoire de base correct est en place.

L'avantage de ce sur-parent est qu'il fonctionne dans le cas où le dernier élément du chemin de destination est un nom de fichier.

Et cela fonctionnera sur OS X.

Jamie McCrindle
la source
6

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

Spongman
la source
1
en fait, ce n'est pas du tout la même chose. sa vous oblige à passer le nom de fichier deux fois (d'où l'indicateur '-t') et il définit les bits d'exécution sur le fichier (d'où l'exemple '-m'). sa ne devrait pas être la meilleure réponse, car elle ne répond pas réellement à la question - contrairement à la mienne.
Spongman
1
Cela fonctionne pour un seul fichier. Prenez simplement en compte qu'il theres'agit du fichier final et non du sous-répertoire final.
kvantour
6

avec tout mon respect pour les réponses ci-dessus, je préfère utiliser rsync comme suit:

$  rsync -a directory_name /path_where_to_inject_your_directory/

exemple:

$ rsync -a test /usr/local/lib/
marcdahan
la source
J'ai essayé et j'obtiens ce résultat: rsync -a bull / some / hoop / ti / doop / ploop RESULTAT rsync: mkdir "/ some / hoop / ti / doop / ploop" a échoué: aucun fichier ou répertoire (2) erreur rsync : erreur dans le fichier IO (code 11) sur main.c (675) [Receiver = 3.1.2]
thebunnyrules
@thebunnyrules vous devez vérifier le chemin avec la commande pwd (regardez: [link] ( access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/… )) et insérez-le correctement dans votre instruction
marcdahan
Si je comprends bien votre suggestion, vous proposez que je spécifie un chemin complet vers la source aka use: "$ (pwd) / bull" dans ma commande au lieu du nom de fichier: bull? Le truc, c'est que rsync crée le répertoire de destination, pas la source (bull). Il me semble que rsync utilise un simple mkdir et non un mkdir -p.
thebunnyrules
1
Très utile d'ailleurs, je pourrais commencer à l'utiliser dans mes scripts. Merci de le recommander.
thebunnyrules
1
rsync est plus prévisible que cp. Par exemple, si je souhaite cp /from/somedir /to-diralors cp se comporte différemment s'il /to-direxiste déjà: s'il /to-direxiste, alors cpcréera /to-dir/some-dir, sinon juste le contenu de /from/somedirsera placé dans /to-dir. Cependant, rsyncse comporte de la même façon au cas où, c'est-à-dire, /to-dir/some-dirsera toujours créé.
JoeAC
5

Juste pour reprendre et donner une solution de travail complète, en une seule ligne. Soyez prudent si vous souhaitez renommer votre fichier, vous devez inclure un moyen de fournir un chemin de répertoire propre à mkdir. $ fdst peut être un fichier ou un dir. Le code suivant devrait fonctionner dans tous les cas.

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

ou spécifique à bash

fsrc=/tmp/myfile.unk
fdst=/tmp/dir1/dir2/dir3/myfile.txt
mkdir -p ${fdst%/*} && cp -p ${fsrc} ${fdst}
Kaalahaan
la source
Merci! Voilà ce dont j'avais besoin. Dans mon cas, je ne sais pas si la destination a un répertoire ou non et si le répertoire existe ou non, donc ce code me donne le répertoire parent que je peux ensuite créer en toute sécurité s'il n'existe pas
xbmono
4

Ajoutez simplement ce qui suit dans votre .bashrc, ajustez si vous en avez besoin. Fonctionne dans Ubuntu.

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

Par exemple, si vous souhaitez copier le fichier "test" dans le répertoire de destination "d" Utiliser,

mkcp test a/b/c/d

mkcp vérifiera d'abord si le répertoire de destination existe ou non, sinon créez-le et copiez le fichier / répertoire source.

DAKOTA DU SUD.
la source
Comme d'autres ont commenté l'autre réponse, vous n'avez pas besoin de tester si le répertoire existe avant d'appeler mkdir -p. Il réussira même s'il existe déjà.
bfontaine
3

Ça le fait pour moi

cp -vaR ./from ./to
Leroy Dunn
la source
Se mettre d'accord. Dans man cp:-R If source_file designates a directory, cp copies the directory and the entire subtree connected at that point.
borracciaBlu
3

J'ai écrit un script de support pour cp, appelé CP (note majuscules) qui est destiné à faire exactement cela. Le script vérifiera les erreurs dans le chemin que vous avez mis (sauf le dernier qui est la destination) et si tout va bien, il fera une étape mkdir -p pour créer le chemin de destination avant de commencer la copie. À ce stade, l'utilitaire cp normal prend le relais et tous les commutateurs que vous utilisez avec CP (comme -r, -p, -rpL sont dirigés directement vers cp). Avant d'utiliser mon script, vous devez comprendre certaines choses.

  • toutes les informations ici sont accessibles en utilisant CP --help. CP --help-all inclut les commutateurs cp de.
  • cp normal ne fera pas la copie s'il ne trouve pas le chemin de destination. Vous n'avez pas un tel filet de sécurité pour les fautes de frappe avec CP. Votre destination sera créée, donc si vous mal orthographiez votre destination en tant que / usrr / share / icons ou / usr / share / icon, c'est ce qui va être créé.
  • cp normal a tendance à modéliser son comportement sur le chemin existant: cp / a / b / c / d variera selon que d existe ou non. si d est un dossier existant, cp copiera b dans celui-ci, créant / c / d / b. Si d n'existe pas, b sera copié dans c et renommé en d. Si d existe mais est un fichier et b est un fichier, il sera écrasé par la copie de b. Si c n'existe pas, cp ne fait pas la copie et quitte.

Le CP n'a pas le luxe de s'inspirer des chemins existants, il doit donc avoir des schémas de comportement très fermes. CP suppose que l'élément que vous copiez est déposé dans le chemin de destination et n'est pas la destination elle-même (c'est-à-dire une copie renommée du fichier / dossier source). Sens:

  • "CP / a / b / c / d" se traduira par / c / d / b si d est un dossier
  • "CP / a / b / c / b" se traduira par / c / b / b si b dans / c / b est un dossier.
  • Si b et d sont tous deux des fichiers: CP / a / b / c / d donnera / c / d (où d est une copie de b). Idem pour CP / a / b / c / b dans les mêmes circonstances.

Ce comportement CP par défaut peut être modifié avec le commutateur "--rename". Dans ce cas, on suppose que

  • "CP --rename / a / b / c / d" copie b dans / c et renomme la copie en d.

Quelques notes de clôture: Comme avec cp, CP peut copier plusieurs éléments à la fois, le dernier chemin répertorié étant supposé être la destination. Il peut également gérer des chemins avec des espaces tant que vous utilisez des guillemets.

CP vérifiera les chemins que vous avez mis et s'assurera qu'ils existent avant de faire la copie. En mode strict (disponible via l'option --strict), tous les fichiers / dossiers copiés doivent exister ou aucune copie n'a lieu. En mode détendu (--relaxed), la copie continuera si au moins un des éléments que vous avez répertoriés existe. Le mode détendu est le mode par défaut, vous pouvez changer le mode temporairement via les commutateurs ou de façon permanente en définissant la variable easy_going au début du script.

Voici comment l'installer:

Dans un terminal non root, faites:

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

Dans gedit, collez l'utilitaire CP et enregistrez:

#!/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
la source
C'est peut-être exagéré, mais assez efficace, fonctionne comme prévu, merci bon monsieur.
Xedret
2

cp a de multiples usages:

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

La réponse de @ AndyRoss fonctionne pour le

cp SOURCE DEST

style de cp, mais fait la mauvaise chose si vous utilisez le

cp SOURCE... DIRECTORY/

style de cp.

Je pense que "DEST" est ambigu sans barre oblique dans cet usage (c'est-à-dire où le répertoire cible n'existe pas encore), c'est peut-être pourquoi cpn'a jamais ajouté une option pour cela.

Voici donc ma version de cette fonction qui applique une barre oblique de fin sur le répertoire dest:

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
}
Riches
la source
2

Juste eu le même problème. Mon approche était de simplement tarer les fichiers dans une archive comme ceci:

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

tar stocke automatiquement les fichiers dans la structure appropriée dans l'archive. Si vous courez

tar xf your_archive.tar

les fichiers sont extraits dans la structure de répertoires souhaitée.

Chris
la source
1

Comme suggéré ci-dessus par help_asap et spongeman, vous pouvez utiliser la commande 'install' pour copier des fichiers dans des répertoires existants ou créer de nouveaux répertoires de destination s'ils n'existent pas déjà.

L'option 1 install -D filename some/deep/directory/filename
copie le fichier dans un répertoire nouveau ou existant et donne les autorisations 755 par défaut au nom de fichier

Option 2 install -D filename -m640 some/deep/directory/filename
selon l'option 1 mais donne 640 autorisations de nom de fichier.

Option 3 install -D filename -m640 -t some/deep/directory/
selon l'option 2, mais cible le nom de fichier dans le répertoire cible de sorte que le nom de fichier n'a pas besoin d'être écrit à la fois dans la source et dans la cible.

Option 4 install -D filena* -m640 -t some/deep/directory/
selon l'option 3, mais utilise un caractère générique pour plusieurs fichiers.

Il fonctionne bien dans Ubuntu et combine deux étapes (création de répertoire puis copie de fichier) en une seule étape.

Creuser
la source
1

C'est très tard, mais cela peut aider une recrue quelque part. Si vous devez créer automatiquement des dossiers, rsync devrait être votre meilleur ami. rsync / path / to / sourcefile / path / to / tragetdir / thatdoestexist /

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

Cela pourrait fonctionner, si vous avez le bon type de rsync.

danuker
la source
Cela ne fonctionne pas pour moi - J'ai obtenu "Aucun fichier ou répertoire" sur le répertoire de destination
Rich
0

Copier de la source vers un chemin inexistant

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

REMARQUE: cette commande copie tous les fichiers

cp –r pour copier tous les dossiers et son contenu

$_ fonctionne comme destination qui est créée dans la dernière commande

développeurprashant
la source
0

Facile

cp -a * /path/to/dst/

devrait faire l'affaire.

Brad D.
la source
Ce n'est pas le cas. Le '/ chemin / vers / dst /' doit exister au préalable.
Shital Shah
-1

Disons que vous faites quelque chose comme

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

où A / B / C / D sont des répertoires qui n'existent pas encore

Une solution possible est la suivante

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

J'espère que cela pourra aider!

sdinesh94
la source