Ordenar y mv archivos según el nombre del archivo (con espacios), de forma recursiva

Ordenar y mv archivos según el nombre del archivo (con espacios), de forma recursiva

Cometí un error y arrojé archivos juntos en el mismo directorio. Afortunadamente puedo ordenarlos según el nombre del archivo: ''' 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg ''' Donde#poco o2en este caso específico es la información de ubicación, por así decirlo. El rango es 0-9 y todos los nombres de archivos tienen la misma longitud, por lo que ese número siempre estará en la misma posición del nombre de archivo (carácter 26, incluidos espacios, flanqueados por guiones bajos). Encontré este gran enlace: ¿Comando para buscar archivos buscando solo una parte de sus nombres?

Sin embargo, no puedo canalizar la salida al comando de movimiento. Intenté buclear la salida en una variable pero tampoco pareció funcionar:

for f in find . -name '*_0_*' ; do  mv "$f" /destination/directory ; done

Según este enlace, es posible que haya puesto el * o algunas comillas en el lugar equivocado:mv: no se puede establecer No existe tal archivo o directorio en el script de shell.

Dicho esto, tengo muchos directorios y me gustaría ordenarlos en una estructura de directorios idéntica en otro lugar:

-Flowers (to be sorted)          -Flowers-buds               -Flowers-stems
     -Roses                          -Roses                      -Roses
        buds.jpg       ===>             buds.jpg     ===>           stems.jpg
        stems.jpg
        petals.jpg
     -Daisies                       -Daisies                    -Daisies
        buds.jpgs                       buds.jpg                   stems.jpg
        stems.jpg
        petals.jpg
     -Tulips                        -Tulips                     -Tulip
        buds.jpgs                       buds.jpg                  stems.jpg
        stems.jpg
        petals.jpg

...y más según ese número (#). ¿Es esto algo práctico en bash? Estoy ejecutando MacOS en la terminal con coreutils instalado, por lo que las herramientas deberían comportarse como GNU Linux, en lugar de Darwin (BSD).

Respuesta1

Puedes hacer esto con findy mv, pero aquí no es el método más sencillo. Estás en macOS, entonceszshestá preinstalado. Zsh viene con una elegante herramienta de cambio de nombre masivo de archivos llamadazmv. Ejecute zshen una terminal, luego en zsh ejecute algo como:

autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'

Explicaciones:

  • -nle dice zmvque muestre lo que haría, pero en realidad no hace nada. Cuando esté satisfecho con lo que muestra, ejecute el comando nuevamente sin -n.
  • El primer argumento que no es una opción es unpatrón comodín. zmvactuará sobre los archivos coincidentes (e ignorará los archivos que no coincidan).
  • [^_]#significa cero o más caracteres distintos de _. Zsh te da acceso a los mismos comodines que bash y más. #es una característica exclusiva de zsh (disponible en bash con una sintaxis diferente) que significa "cualquier número del carácter anterior". El patrón [^_]#_[0-9]_*coincide con cualquier nombre de archivo que contenga un solo dígito entre dos guiones bajos y ningún otro guión bajo antes de ese dígito.
  • Los paréntesis [0-9]hacen que el dígito esté disponible como $1en el texto de reemplazo.
  • El texto de reemplazo puede usar ${1}, ${2}, etc. para hacer referencia a fragmentos entre paréntesis en el patrón fuente y ${f}para hacer referencia al nombre original completo.

Por ejemplo, el fragmento de arriba se mueve 2019-02-19 20.18.58.ndpi_2_2688_2240.jpga /elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg. Su pregunta no describe qué se debe mover y dónde se debe mover con mucha precisión, pero debería poder obtener lo que desea modificando el comando anterior. Si no puede resolverlo, edite su pregunta para agregar ejemplos específicos de lo que debe y no debe coincidir.

Si tiene archivos en subdirectorios, puede usarlos */al comienzo del patrón. Si necesita hacer coincidir el directorio para hacer referencia a él , debe poner paréntesis alrededor de : no puede poner paréntesis alrededor de una barra diagonal. Si desea recorrer el directorio de forma recursiva y hacer coincidir archivos en cualquier deuda, utilice for${NUM}***/globo recursivo. Como excepción, debe utilizar (**/)para acceder a la ruta del directorio en el texto de reemplazo como . En casos como este, a menudo es más fácil no usar paréntesis y en su lugar usar${NUM}modificadoresContinúe ${f}para extraer partes de la ruta original en su totalidad. Por ejemplo, haga el mismo cambio de nombre que arriba, pero moviendo archivos del directorio actual a una estructura paralela en /elsewhere, pero con un nivel adicional 0, 1, etc. justo antes del nombre del archivo:

autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'

Si es difícil hacer coincidir los archivos según su nombre, un enfoque diferente sería hacerlo según su hora de cambio¹. Si acaba de copiar un montón de archivos a un directorio que no ha cambiado durante un tiempo, los archivos nuevos son los que tienen una hora de cambio reciente. Puede hacer coincidir archivos por hora de cambio en zsh con elcalificador globalc. Por ejemplo, para enumerar los archivos modificados por última vez en la última hora:

ls -lctr *(ch-1)

En lugar de encontrar una hora límite, puede consultar lanortearchivos modificados más recientemente con los calificadores globales oc(para ordenar aumentando ctime) y (para mantener solo el primero[1,N]nortepartidos). Por ejemplo, si sabe que acaba de mover 42 archivos a ese directorio y no ha cambiado nada desde entonces:

ls -lctr *(oc[1,42])

Si desea utilizar calificadores globales con zmv, debe pasar la -Qopción. Por ejemplo, para mover archivos a un directorio según su nombre como se indica arriba, pero ignorar todos los archivos que no han cambiado en la última hora:

zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'

zmvtiene algunas medidas de seguridad para comprobar que no sobrescribirá archivos y que no habrá colisiones (dos archivos de origen con el mismo nombre de destino). Si tiene una gran cantidad de archivos, estas medidas de seguridad pueden llevar tiempo. Si zmves demasiado lento y la estructura de su cambio de nombre garantiza que no habrá colisiones, puede codificar el cambio de nombre específico que está realizando en un bucle. Zsh tiende a vencer a bash incluso si no lo usas zmvgracias a que es más agradablegloboyexpansión de parámetrosmecanismos. Para el rendimiento, puedescargar dinámicamenteun módulo que contieneincorporados para la manipulación de archivos.

Por ejemplo, para mover archivos normales desde el directorio actual a una jerarquía completamente nueva si su nombre contiene _0_:

zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
  zf_mkdir -p /destination/directory/$x:h
  zf_mv ./$x /destination/directory/$x
done

( :hes otra característica más de zsh: amodificador de historia.)

Para tomar el primer número que está entre dos guiones bajos en el nombre del archivo y colocar los archivos en un directorio con el nombre de ese número, deberá extraer el número. zmvhace esto automáticamente para grupos entre paréntesis, aquí debemos hacerlo a mano.

zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
  suffix=${${source:t}#*_<->_}
  n=${${source%$suffix}##*_}
  destination=${source:h}/$n/${source:t}
  zf_mkdir -p /destination/directory/$destination:h
  zf_mv ./$source /destination/directory/$destination
done

Si desea conocer otros métodos para cambiar el nombre de archivos en masa, puede navegarpreguntas etiquetadas renameen este sitio.

¹ La hora de cambio de inodo de un archivo, denominada “hora de cambio” o “ctime” para abreviar, es la hora en que se movió por última vez el archivo o en la que se cambiaron sus atributos por última vez, como los permisos. Es diferente de la hora de modificación (mtime).

Respuesta2

El shell está dividiendo la entrada en espacios en blanco. Puedes usar bash globbing con el recursivo **para dividir los nombres de archivos correctamente:

shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done

Si todos van al mismo lugar, el bucle no es necesario:

shopt -s globstar
mv **/*_0_*  /dest/

... o use find -exec, que pasa nombres de archivos directamente desde find a una exec()llamada, sin la participación del shell:

find . -name '*_0_*' -exec mv {} /dest/ \;

O use readmás find -print0para dividir en nulo. Es excesivo para este problema, útil cuando se hace globalización y find -execno se está a la altura de la tarea:

while IFS=  read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )

Para cambiar el destino de nivel superior según el nombre del archivo, como en su ejemplo, corte partes del nombre del archivo usando ${var#remove_from_start}y ${var%remove_from_end}. Los comodines en los patrones de eliminación eliminarán lo menos posible; para eliminar tanto como sea posible, duplique el carácter de operación, como en ${…##…}o ${…%%…}:

    shopt -s globstar
    for f in **/*_0_*; do            # Flowers/Roses/stems.jpg 
      file=${f##*/}                  # stems.jpg
      base=${file%.*}                # stems
      path=${f%/*}                   # Flowers/Roses
      toppath=${path%%/*}            # Flowers
      subpath=${path#*/}             # Roses
      dest=$toppath-$base/$subpath/  # Flowers-stems/Roses/
      [[ -d $dest ]] || mkdir -p "$dest"
      mv "$f" "$dest"
    done

información relacionada