¿Hay alguna manera de hacer que mv cree el directorio al que se moverá si no existe?

275

Entonces, si estoy en mi directorio de inicio y quiero mover foo.c a ~ / bar / baz / foo.c, pero esos directorios no existen, ¿hay alguna forma de crear esos directorios automáticamente, de modo que solo tendrías que escribir

mv foo.c ~/bar/baz/ 

y todo saldría bien? Parece que podría alias mv a un script bash simple que verificaría si esos directorios existían y si no llamaría a mkdir y luego a mv, pero pensé en verificar si alguien tenía una mejor idea.

Paul Wicks
fuente

Respuestas:

294

¿Qué tal este one-liner (en bash):

mkdir --parents ./some/path/; mv yourfile.txt $_

Desglosando eso:

mkdir --parents ./some/path

crea el directorio (incluidos todos los directorios intermedios), después de lo cual:

mv yourfile.txt $_

mueve el archivo a ese directorio ($ _ se expande al último argumento pasado al comando de shell anterior, es decir: el directorio recién creado).

No estoy seguro de hasta qué punto esto funcionará en otros shells, pero podría darle algunas ideas sobre qué buscar.

Aquí hay un ejemplo usando esta técnica:

$ > ls
$ > touch yourfile.txt
$ > ls
yourfile.txt
$ > mkdir --parents ./some/path/; mv yourfile.txt $_
$ > ls -F
some/
$ > ls some/path/
yourfile.txt
KarstenF
fuente
32
Agradable con el '$ _'.
dmckee --- ex-gatito moderador
1
¿Por qué la obsesión con lo redundante en ./todas partes? Los argumentos no son comandos en RUTA sin el .directorio ...
Jens
3
para los novatos, los padres pueden acortarse a -p
sakurashinken
8
Curiosamente, --parentsno está disponible en macOS 10.13.2. Necesitas usar -p.
Joshua Pinter
1
No funciona si mueve un archivo, por ejemplo ./some/path/foo. En este caso, el comando debería ser:mkdir -p ./some/path/foo; mv yourfile.txt $_:h
hhbilly
63
mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt
Billmc
fuente
2
¿Soy el único en darse cuenta de que este código no tiene sentido? Crea recursivamente la ruta absoluta al archivo existente (lo que significa que esta ruta ya existe) y luego mueve el archivo a una ubicación (que en su ejemplo no existe).
vdegenne
1
@ user544262772 GNU coreutils ' dirnameno requiere que exista su argumento, es pura manipulación de cadenas. No puedo hablar por otras distribuciones. No hay nada malo con este código de hecho.
TroyHurts
Esta es la respuesta que se automatiza más fácilmente, creo. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Kyle
23

Guardar como un script llamado mv o mv.sh

#!/bin/bash
# mv.sh
dir="$2"
tmp="$2"; tmp="${tmp: -1}"
[ "$tmp" != "/" ] && dir="$(dirname "$2")"
[ -a "$dir" ] ||
mkdir -p "$dir" &&
mv "$@"

O coloque al final de su archivo ~ / .bashrc como una función que reemplaza el mv predeterminado en cada terminal nueva. El uso de una función permite que bash lo mantenga en memoria, en lugar de tener que leer un archivo de script cada vez.

function mv ()
{
    dir="$2"
    tmp="$2"; tmp="${tmp: -1}"
    [ "$tmp" != "/" ] && dir="$(dirname "$2")"
    [ -a "$dir" ] ||
    mkdir -p "$dir" &&
    mv "$@"
}

Estos basados ​​en la presentación de Chris Lutz.

Sepero
fuente
3
Esto responde la pregunta con mayor precisión en mi humilde opinión. El usuario desea un mvcomando mejorado . Especialmente útil para las secuencias de comandos, es decir, no necesariamente desea ejecutar las comprobaciones y ejecutarlas en mkdir -pcualquier momento que necesite usar mv. Pero dado que desearía el comportamiento de error predeterminado mv, cambié el nombre de la función a mvp, para saber cuándo podría estar creando directorios.
Brian Duncan
Parece la mejor idea de solución, pero la implementación se bloquea mucho.
Ulises Layera
2
@UlisesLayera Modifiqué el algoritmo para hacerlo más robusto. No debería estrellarse ahora
Sepero
¿Alguien podría explicar el significado de [ ! -a "$dir" ]que he realizado experimentos con la mitad correcta siendo verdadero y falso, ambos evaluados como verdaderos? Por el bien de los demás, tmp = "$ {tmp: -1}" parece tomar el último carácter del archivo para asegurarse de que no sea una ruta (/)
bazz
@bazz Debería estar haciendo "si $ dir no existe, entonces continúe". Desafortunadamente, tienes razón, hay un error al usar "! -A", y no sé por qué. He editado un poco el código y ahora debería funcionar.
Sepero
13

Puedes usar mkdir:

mkdir -p ~/bar/baz/ && \
mv foo.c ~/bar/baz/

Un script simple para hacerlo automáticamente (no probado):

#!/bin/sh

# Grab the last argument (argument number $#)    
eval LAST_ARG=\$$#

# Strip the filename (if it exists) from the destination, getting the directory
DIR_NAME=`echo $2 | sed -e 's_/[^/]*$__'`

# Move to the directory, making the directory if necessary
mkdir -p "$DIR_NAME" || exit
mv "$@"
extraño
fuente
Cuando ejecuto "$ dirname ~? Bar / baz /", obtengo "/ home / dmckee / bar", que no es lo que quieres aquí ...
dmckee --- ex-gatito moderador
@dmckee, Ah, tienes razón. ¿Alguna idea sobre cómo resolver esto? Si ingresa ~ / bar / baz, ya sea (a) desea copiar a ~ / bar / y renombrar a baz, o (b) copiar a ~ / bar / baz /. ¿Cuál es la mejor herramienta para el trabajo?
extraño
@dmckee, he usado regexp / sed para encontrar una solución. ¿Funciona a tu gusto? =]
extraño
Bien, excepto que $ 2 no es el último argumento a menos que $ # = 2. Tengo un programa, la, que imprime su último argumento. Solía ​​usarlo en una versión del comando cp (para agregar el directorio actual si el último argumento no era un directorio). Incluso los proyectiles modernos admiten $ 1 ... $ 9 solamente; Un script Perl puede ser mejor.
Jonathan Leffler
@Leffler, no es cierto: sé que bash admite más de 9 argumentos (usar $ {123} es un método). No conozco a Perl, así que siéntete libre de responder tú mismo. =]
extraño
7

Parece que la respuesta es no :). Realmente no quiero crear un alias o función solo para hacer esto, a menudo porque es único y ya estoy en el medio de escribir el mvcomando, pero encontré algo que funciona bien para eso:

mv *.sh  shell_files/also_with_subdir/ || mkdir -p $_

Si mvfalla (el directorio no existe), creará el directorio (que es el último argumento del comando anterior, también lo $_tiene). Simplemente ejecute este comando, luego upvuelva a ejecutarlo, y esta vez mvdebería tener éxito.

Palmadita
fuente
5

¿El siguiente script de shell, tal vez?

#!/bin/sh
if [[ -e $1 ]]
then
  if [[ ! -d $2 ]]
  then
    mkdir --parents $2
  fi
fi
mv $1 $2

Esa es la parte básica. Es posible que desee agregar un poco para verificar los argumentos, y puede que desee que el comportamiento cambie si el destino existe o si el directorio fuente existe o no existe (es decir, no sobrescriba algo que no existe) .

Chris Lutz
fuente
1
¡Con su código, el movimiento no se realiza si el directorio no existe!
extraño
1
Y te perdiste la bandera "-p" de mkdir.
dmckee --- ex-gatito moderador
Si no existe un directorio, ¿por qué crearlo si solo va a moverlo? (-p flag fixed)
Chris Lutz
Quiero decir, cuando ejecuta el script y el directorio $ 2 no existe, se crea pero el archivo no se copia.
extraño
dio una construcción simple y agregó información sobre cómo debería expandirse. ¿Por qué votar hacia abajo?
ypnos
5

rsync comando puede hacer el truco solo si el último directorio en la ruta de destino no existe, por ejemplo, para la ruta de destino de ~/bar/baz/if barexiste pero bazno existe , entonces se puede usar el siguiente comando:

rsync -av --remove-source-files foo.c ~/bar/baz/

-a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
-v, --verbose               increase verbosity
--remove-source-files   sender removes synchronized files (non-dir)

En este caso, el bazdirectorio se creará si no existe. Pero si ambos bary bazno existen, rsync fallará:

sending incremental file list
rsync: mkdir "/root/bar/baz" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(657) [Receiver=3.1.2]

Entonces, básicamente, debería ser seguro de usar rsync -av --remove-source-files como un alias para mv.

Sepehr
fuente
4

La forma más sencilla de hacerlo es:

mkdir [directory name] && mv [filename] $_

Supongamos que descargué archivos pdf ubicados en mi directorio de descargas ( ~/download) y quiero moverlos a un directorio que no existe (digamosmy_PDF ).

Escribiré el siguiente comando (asegurándome de que mi directorio de trabajo actual sea ~/download):

mkdir my_PDF && mv *.pdf $_

Puede agregar una -popción a mkdirsi desea crear subdirectorios así: (se supone que quiero crear un subdirectorio llamado python):

mkdir -p my_PDF/python && mv *.pdf $_
Abdoul Seibou
fuente
2

Más tonto, pero trabajando de la manera:

mkdir -p $2
rmdir $2
mv $1 $2

Cree el directorio con mkdir -p, incluido un directorio temporal que comparta el nombre del archivo de destino, luego elimine ese directorio con un simple rmdir, luego mueva su archivo a su nuevo destino. Sin embargo, creo que la respuesta usando dirname es probablemente la mejor.

Blarn Blarnson
fuente
Sí, un poco tonto. :-) Si $ 2 es un directorio (como en la pregunta original), falla miserablemente, ya que el último directorio se eliminará, por lo que 'mv' fallará. Y si $ 2 ya existe, también intenta eliminarlo.
Tylla
¡Gracias! Esto es exactamente lo que necesitaba lograr: mv ./old ./subdir/new donde subdir aún no existe
Mike Murray
2
((cd src-path && tar --remove-files -cf - files-to-move) | ( cd dst-path && tar -xf -))
svaardt
fuente
1

Código:

if [[ -e $1 && ! -e $2 ]]; then
   mkdir --parents --verbose -- "$(dirname -- "$2")"
fi
mv --verbose -- "$1" "$2"

Ejemplo:

argumentos: "d1" "d2 / sub"

mkdir: created directory 'd2'
renamed 'd1' -> 'd2/sub'
John Doe
fuente
1

Esto se moverá foo.cal nuevo directorio bazcon el directorio padre bar.

mv foo.c `mkdir -p ~/bar/baz/ && echo $_`

La -popción mkdircreará directorios intermedios según sea necesario.
Sin-p todos los directorios en el prefijo de ruta ya debe existir.

Todo lo que está dentro de los backticks ``se ejecuta y la salida se devuelve en línea como parte de su comando.
Como mkdirno devuelve nada, solo la salida de echo $_se agregará al comando.

$_hace referencia al último argumento al comando ejecutado anteriormente.
En este caso, devolverá la ruta a su nuevo directorio ( ~/bar/baz/) pasado al mkdircomando.


Descomprimí un archivo sin dar un destino y quería mover todos los archivos excepto demo-app.zipde mi directorio actual a un nuevo directorio llamado demo-app.
La siguiente línea hace el truco:

mv `ls -A | grep -v demo-app.zip` `mkdir -p demo-app && echo $_`

ls -Adevuelve todos los nombres de archivo, incluidos los archivos ocultos ( excepto los implícitos .y.. ).

El símbolo de canalización |se utiliza para canalizar la salida del lscomando grep( una utilidad de búsqueda de texto sin formato en la línea de comandos ).
El -vindicador dirige grepa buscar y devolver todos los nombres de archivo, excepto demo-app.zip.
Esa lista de archivos se agrega a nuestra línea de comandos como argumentos de origen para el comando mover mv. El argumento objetivo es la ruta al nuevo directorio pasado a mkdirreferenciado usando $_y salida usando echo.

Evan Wunder
fuente
1
Este es un buen nivel de detalle y explicación, y especialmente para una primera publicación. ¡Gracias!
Jeremy Caney
¡Gracias, @JeremyCaney! ¡Estoy emocionado de comenzar a ayudar!
Evan Wunder
0

Incluso puedes usar extensiones de llaves:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • que crea 3 directorios (directorio1, directorio2, directorio3),
    • y en cada uno de ellos dos subdirectorios (subdirectorio1, subdirectorio2),
      • y en cada uno de ellos dos subdirectorios (subsubdirectory1 y subsubdirectory2).

Tienes que usar bash 3.0 o más reciente.

Stefan Gruenwald
fuente
55
Interesante, pero no responde la pregunta del OP.
Alexey Feldgendler
0
$what=/path/to/file;
$dest=/dest/path;

mkdir -p "$(dirname "$dest")";
mv "$what" "$dest"
cab404
fuente
1
para que sea un script sh independiente, simplemente reemplace $ dest y $ src con $ 1 y $ 2
cab404
0

Mi solución de una cadena:

test -d "/home/newdir/" || mkdir -p "/home/newdir/" && mv /home/test.txt /home/newdir/
Andrey
fuente
0

Puede encadenar comandos: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
o el script de shell está aquí:

#!/bin/bash
mkdir $2 | mv $1 $2

1. Abra cualquier editor de texto
2. Copie y pegue el script de shell.
3. Guárdelo como mkdir_mv.sh
4. Ingrese el directorio de su script: cd your/script/directory
5. Cambie el modo de archivo : chmod +x mkdir_mv.sh
6. Establezca el alias : alias mv="./mkdir_mv.sh"
Ahora, cada vez que ejecute el comando mv, se creará un directorio móvil si no existe.

Sharofiddin
fuente
0

Basado en un comentario en otra respuesta, aquí está mi función de shell.

# mvp = move + create parents
function mvp () {
    source="$1"
    target="$2"
    target_dir="$(dirname "$target")"
    mkdir --parents $target_dir; mv $source $target
}

Incluya esto en .bashrc o similar para que pueda usarlo en todas partes.

Ben Winding
fuente