¿mover archivos hacia arriba un nivel anteponiendo el nombre de la carpeta al nombre del archivo?

¿mover archivos hacia arriba un nivel anteponiendo el nombre de la carpeta al nombre del archivo?

Entorno de prueba:

  • crear directorios desde 0hasta 9y llenarlos con archivos desde ahastaz
    $ mkdir {0..9} && touch {0..9}/{a..z}
    
  • Consecuencia de find:
    $ find -type f
    ./0/a
    ./0/b
    
    ...
    
    ./9/y
    ./9/z
    

¿Cómo puedo transformar la salida para que se vea así?

./0a    
./0b

...

./9y
./9z

Respuesta1

Con el comando independiente de Perl rename:

rename -n 's|/||' */*

Si todo se ve bien, elimine -ny luego elimine los directorios vacíos restantes.

rmdir {0..9}

Respuesta2

Con herramientas POSIX,

find . -type f -exec sh -c 'for f; do dirn="${f%/*}"; mv -- "$f" "$dirn${f##*/}"; done' sh {} +

findinvoca un shell y ejecuta el bucle for para cada archivo.

  • dirn="${f%/*}"obtiene el nombre del directorio principal excluyendo todo lo que está después (e incluyendo) la última barra diagonal en el nombre del archivo.

  • ${f##*/}obtiene el nombre del archivo sin la ruta excluyendo todo lo que está antes (e incluyendo) la última barra diagonal en el nombre del archivo.

Esas dos construcciones de caparazón hacen lo mismo quedirnameybasename, por lo que una forma equivalente del comando es

find . -type f -exec sh -c 'for f; do mv -- "$f" "$(dirname "$f")$(basename "$f")"; done' sh {} +

, aunque éste debería ser más lento ya que necesita generar dos procesos adicionales para cada archivo.

Si no necesita descender a más de un directorio, como muestra su ejemplo, un simple bucle de shell lo hace:

for f in */*; do dirn="${f%/*}"; echo mv -- "$f" "$dirn${f##*/}"; done

información relacionada