Secuencias de comandos de shell de UNIX: ¿cómo mover archivos de forma recursiva en un directorio?

8

Tengo una gran cantidad de archivos pequeños, f, organizados en una estructura de directorios como esta:

/A/B/C/f

Hay 11 directorios en el nivel de A, cada uno con aproximadamente 100 directorios en el nivel de B, cada uno con aproximadamente 30 directorios en el nivel de C, cada uno con un archivo f.

¿Cómo muevo todos los archivos un nivel más arriba? Por ejemplo, dado este conjunto de archivos ...

/ A / B / C / f1
/ A / B / C / f2
/ A / B / C / f3
/ A / B / C / f4

Quiero que el directorio /A/B/contenga 4 archivos, de f1 a f4. Eliminar directorios C no es necesario.

Estoy esperando que esto es un problema resuelto, posiblemente con la participación find, xargsy whatnot. ¿Algunas ideas?

Salud,

James

jl6
fuente

Respuestas:

9

Es bastante simple con GNU find (como se encuentra en Linux) o cualquier otro hallazgo que admita -execdir:

find A -type f -execdir mv -i {} .. \;

Con un estándar find:

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

Con zsh:

zmv -Q -o-i 'A/(**/)*/(*)(.)' 'A/$1$2'

Si la estructura del directorio siempre tiene el mismo nivel de anidación, no necesita ningún recorrido recursivo (pero primero elimine los directorios vacíos):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
Gilles 'SO- deja de ser malvado'
fuente
1

Para ese conjunto de archivos, esto haría:

$ cd /A/B/C/
$ mv ./* ../

Pero espero que su problema sea algo más complicado ... No puedo responder a esto ... No estoy muy seguro de cómo es su estructura de directorios ... ¿Podría aclararlo?

Stack Overflow está muerto
fuente
Obviamente, esto funcionará para cualquier directorio / A / B / C, pero como tengo alrededor de 30000 de estos directorios, espero un comando que se pueda ejecutar en la parte superior de la jerarquía para ocuparse de todos ellos a la vez.
jl6
1
@ jl6 Ya veo, ¿y solo los archivos necesitan moverse? Entonces, C no tiene que moverse a B, y B no a A, etc ...
Stack Overflow está muerto
Eso es correcto: la operación solo debe realizarse en archivos, en el nivel más bajo de la jerarquía, y solo deben moverse un nivel hacia arriba.
jl6
0

Mi primera suposición es

$ find A -type f -exec mv {} .. \;  

siempre y cuando no lo especifique -depth, debería estar bien. NO lo he probado, así que pruébalo antes de comprometerte.

hotei
fuente
¿Qué hace find cuando hay dos archivos con el mismo nombre?
Buscar no es el problema, el comando "mv" incrustado sí lo es. Los nombres duplicados no serían buenos ya que mv no va a cambiar el nombre, se va a sobrescribir. Podrías usar mv -i para ayudar un poco, pero con más de 30,000 archivos que pueden ser dolorosos. Si necesita tener tanto control sobre la situación, es posible que necesite un programa escrito en un lenguaje de secuencias de comandos (mi elección sería python, YMMV).
hotei
No he usado "copias de seguridad" con mv, pero al mirar la página del manual parece que podría ser una solución a su problema de nombres falsos. "encuentra A-tipo f -exec mv - copia de seguridad numerada {} .. \;" podría funcionar (tenga en cuenta que es un doble guión en --backup)
hotei
El ejemplo de búsqueda dado moverá todos los archivos en el nivel más bajo a un directorio por encima de A (creo que el ".." está siendo interpretado por el shell antes de pasar a buscar / mv?). Necesito que los archivos se muevan solo un nivel hacia arriba en la jerarquía. Una aclaración adicional: no habrá nombres de archivo duplicados, siempre y cuando los archivos solo se muevan UN nivel hacia arriba.
jl6
@hotei: los mvcomandos se ejecutan en el directorio actual, por lo ..que no hace lo que espera. Vea mi respuesta para soluciones. @ jl6: el shell no tiene nada que ver .., eso lo maneja el núcleo.
Gilles 'SO- deja de ser malvado'
0

Si sólo desea mover los archivos que se encuentran en los directorios de las hojas (es decir, que no quiere moverse /A/B/filea /Asi Bcontiene subdirectorios), entonces aquí hay un par de maneras de hacerlo:

Ambos requieren esto

leaf ()
{
    find $1 -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
}
shopt -s nullglob

Este funciona:

leaf A | while read -r dir
do
    for file in "$dir"/*
    do
        parent=${dir%/*}
        if [[ -e "$parent/${file##*/}" ]]
        then
            echo "not moved: $file"
        else
            mv "$file" "$parent"
        fi
    done
done

Esto será más rápido, pero no le gustan los directorios de origen vacíos:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
Pausado hasta nuevo aviso.
fuente