Linux: copie y cree el directorio de destino si no existe

Quiero un comando (o probablemente una opción para cp) que crea el directorio de destino si no existe.

Ejemplo:

cp -? file /path/to/copy/file/to/is/very/deep/there 

 test -d "$d" || mkdir -p "$d" && cp file "$d" 

(no hay tal opción para cp ).

Si ambos de los siguientes son verdaderos:

  1. Está utilizando la versión GNU de cp (y no, por ejemplo, la versión para Mac), y
  2. Está copiando de una estructura de directorios existente y solo necesita volver a crearla

entonces puedes hacer esto con la bandera --parents de cp . Desde la página de información (que se puede ver en http://www.gnu.org/software/coreutils/manual/html_node/cp-invocation.html#cp-invocation o con info cp o 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. 

Ejemplo:

 /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 

Respuesta corta

Para copiar myfile.txt a /foo/bar/myfile.txt , use:

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

¿Como funciona esto?

Hay algunos componentes para esto, así que cubriré toda la syntax paso a paso.

La utilidad mkdir , tal como se especifica en el estándar POSIX , crea directorios. El argumento -p , según los documentos, hará que mkdir

Crear cualquier componente intermedio de ruta faltante

lo que significa que al llamar a mkdir -p /foo/bar , mkdir creará /foo y /foo/bar si /foo aún no existe. (Sin -p , arrojará un error en su lugar.

El operador && list, como está documentado en el estándar POSIX (o en el manual Bash si lo prefiere), tiene el efecto de que cp myfile.txt $_ solo se ejecuta si mkdir -p /foo/bar ejecuta con éxito. Esto significa que el comando cp no intentará ejecutarse si mkdir falla por una de las muchas razones por las que podría fallar .

Finalmente, el $_ que pasamos como segundo argumento para cp es un “parámetro especial” que puede ser útil para evitar repetir largos argumentos (como rutas de archivos) sin tener que almacenarlos en una variable. Según el manual Bash , esto:

se expande al último argumento del comando anterior

En este caso, ese es el /foo/bar que pasamos a mkdir . Entonces, el comando cp expande a cp myfile.txt /foo/bar , que copia myfile.txt en el directorio /foo/bar recién creado.

Tenga en cuenta que $_ no forma parte del estándar POSIX , por lo que, en teoría, una variante de Unix podría tener un shell que no admita esta construcción. Sin embargo, no sé de ningún shells moderno que no sea compatible con $_ ; sin duda Bash, Dash y zsh todos lo hacen.


Una nota final: el comando que he dado al inicio de esta respuesta asume que los nombres de tu directorio no tienen espacios. Si estás tratando con nombres con espacios, necesitarás citarlos para que las diferentes palabras no se tratan como argumentos diferentes para mkdir o cp . Entonces su comando realmente se vería así:

 mkdir -p "/my directory/name with/spaces" && cp "my filename with spaces.txt" "$_" 

Una pregunta tan antigua, pero tal vez pueda proponer una solución alternativa.

Puede usar el progtwig de install para copiar su archivo y crear la ruta de destino “sobre la marcha”.

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

Sin embargo, hay algunos aspectos a tener en cuenta:

  1. necesita especificar también el nombre del archivo de destino , no solo la ruta de destino
  2. el archivo de destino será ejecutable (al menos, por lo que vi de mis pruebas)

Puede modificar fácilmente el # 2 agregando la opción -m para establecer permisos en el archivo de destino (ejemplo: -m 664 creará el archivo de destino con los permisos rw-rw-r-- , al igual que crear un nuevo archivo con el touch ) .


Y aquí está el desvergonzado vínculo con la respuesta que me inspiró =)

Función de Shell que hace lo que quiere, llamándola copia “enterrada” porque hace un agujero para que viva el archivo:

 bury_copy() { mkdir -p `dirname $2` && cp "$1" "$2"; } 

Aquí hay una manera de hacerlo:

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

dirname le dará el padre del directorio o archivo de destino. mkdir -p `dirname …` creará ese directorio asegurando que cuando llame a cp -r, el directorio base correcto esté en su lugar.

La ventaja de esto sobrepapadores es que funciona para el caso donde el último elemento en la ruta de destino es un nombre de archivo.

Y funcionará en OS X.

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

con todo mi respeto por las respuestas anteriores, prefiero usar rsync de la siguiente manera:

 $ rsync -a directory_name /path_where_to_inject_your_directory/ 

ejemplo:

 $ rsync -a test /usr/local/lib/ 

Simplemente agregue lo siguiente en su .bashrc, ajuste si lo necesita. Funciona en Ubuntu.

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

Por ejemplo, si desea copiar el archivo ‘prueba’ al directorio de destino ‘d’ Uso,

 mkcp test a/b/c/d 

mkcp primero comprobará si el directorio de destino existe o no; si no, realice y copie el archivo / directorio de origen.

Esto lo hace por mí

 cp -vaR ./from ./to 

cp tiene múltiples usos:

 $ 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 respuesta de AndyRoss funciona para

 cp SOURCE DEST 

estilo de cp , pero hace lo incorrecto si usas el

 cp SOURCE... DIRECTORY/ 

estilo de cp .

Creo que “DEST” es ambiguo sin una barra inclinada en este uso (es decir, donde el directorio de destino aún no existe), que tal vez sea la razón por la cual cp nunca ha agregado una opción para esto.

Así que aquí está mi versión de esta función que impone una barra al final en el directorio de destino:

 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 } 

Solo para reanudar y dar una solución de trabajo completa, en una línea. Tenga cuidado si desea cambiar el nombre de su archivo, debe incluir una forma de proporcionar una ruta de acceso de directorios limpia a mkdir. $ fdst puede ser archivo o directorio. El siguiente código debería funcionar en cualquier caso.

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

o bash específico

 fsrc=/tmp/myfile.unk fdst=/tmp/dir1/dir2/dir3/myfile.txt mkdir -p ${fdst%/*} && cp -p ${fsrc} ${fdst} 
 rsync file /path/to/copy/file/to/is/very/deep/there 

Esto podría funcionar, si tiene el tipo correcto de rsync .

Copiar desde el origen a una ruta no existente

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

NOTA: este comando copia todos los archivos

cp –r para copiar todas las carpetas y su contenido

$_ trabajo como destino que se creó en el último comando

Escribí un script de soporte para cp, llamado CP (note mayúsculas) que tiene la intención de hacer exactamente esto. El script verificará si hay errores en la ruta que ha ingresado (excepto el último que es el destino) y si todo está bien, hará un paso mkdir -p para crear la ruta de destino antes de comenzar la copia. En este punto, la utilidad cp regular se hace cargo y cualquier conmutador que utilice con CP (como -r, -p, -rpL recibe canalización directamente a cp). Antes de usar mi script, hay algunas cosas que debes entender.

  • toda la información aquí se puede acceder haciendo CP –help. CP –help-all incluye los conmutadores de cp.
  • el CP regular no hará la copia si no encuentra la ruta de destino. No tiene una red de seguridad para errores tipográficos con CP. Tu destino será creado, así que si escribes mal tu destino como / usrr / share / icons o / usr / share / icon, eso es lo que se creará.
  • la CP regular tiende a modelar su comportamiento en la ruta existente: cp / a / b / c / d variará dependiendo de si d existe o no. si d es una carpeta existente, cp copiará b en ella, haciendo / c / d / b. Si d no existe, b se copiará en c y se renombrará a d. Si d existe pero es un archivo yb es un archivo, se sobrescribirá con la copia de b. Si c no existe, cp no hace la copia y sale.

CP no tiene el lujo de tomar señales de las rutas existentes, por lo que tiene que tener algunos patrones de comportamiento muy firmes. CP supone que el elemento que está copiando se está descartando en la ruta de destino y no es el destino mismo (es decir, una copia renombrada del archivo / carpeta fuente). Sentido:

  • “CP / a / b / c / d” dará como resultado / c / d / b si d es una carpeta
  • “CP / a / b / c / b” dará como resultado / c / b / b si b in / c / b es una carpeta.
  • Si ambos byd son archivos: CP / a / b / c / d dará como resultado / c / d (donde d es una copia de b). Lo mismo para CP / a / b / c / b en la misma circunstancia.

Este comportamiento de CP predeterminado se puede cambiar con el interruptor “–rename”. En este caso, se supone que

  • “CP –rename / a / b / c / d” está copiando b en / c y cambiando el nombre de la copia a d.

Algunas notas de cierre: al igual que con cp, CP puede copiar múltiples elementos a la vez con la última ruta que se supone que es el destino. También puede manejar rutas con espacios siempre que use comillas.

CP comprobará las rutas que ingresa y se asegurará de que existan antes de hacer la copia. En modo estricto (disponible a través del – interruptor de restricción), todos los archivos / carpetas que se copian deben existir o no se realiza ninguna copia. En modo relajado (–relaxed), la copia continuará si existe al menos uno de los elementos que enumeró. El modo relajado es el predeterminado, puede cambiar el modo temporalmente a través de los interruptores o permanentemente estableciendo la variable easy_going al comienzo del script.

He aquí cómo instalarlo:

En un terminal no raíz, haz:

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

En gedit, pega la utilidad CP y guarda:

 #!/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 

Digamos que estás haciendo algo como

cp archivo1.txt A / B / C / D / archivo.txt

donde A / B / C / D son directorios que aún no existen

Una posible solución es la siguiente

 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 

¡Espero que ayude!