Acoplar directorio pero conservar nombres de directorio en nuevo nombre de archivo

10

¿Cómo puedo aplanar un directorio en el siguiente formato?

Antes de: ./aaa/bbb/ccc.png

Después: ./aaa-bbb-ccc.png

Nathan JB
fuente
PD: Tenga en cuenta también los nombres de archivo con caracteres extraños (por ejemplo, espacios)
Nathan JB
3
También deberá especificar cómo desea manejar el posible caso donde ./foo/bar/baz.pngy ./foo-bar-baz.pngambos existen. ¿Asumo que no quieres reemplazar el último con el primero?
jw013
En mi caso, es irrelevante, ¡pero la respuesta debería explicarlo para que otros puedan confiar en él! ¡Gracias!
Nathan JB

Respuestas:

7

Advertencia: escribí la mayoría de estos comandos directamente en mi navegador. Advertencia lector.

Con zsh y zmv :

zmv -o -i -Qn '(**/)(*)(D)' '${1//\//-}$2'

Explicación: El patrón **/*coincide con todos los archivos en subdirectorios del directorio actual, de forma recursiva (no coincide con los archivos del directorio actual, pero no es necesario cambiarles el nombre). Los primeros dos pares de paréntesis son grupos a los que se puede hacer referencia como $1y $2en el texto de reemplazo. El par final de paréntesis agrega el D calificador global para que no se omitan los archivos de puntos. -o -isignifica pasar la -iopción a mvpara que se le pregunte si se sobrescribirá un archivo existente.


Con solo herramientas POSIX:

find . -depth -exec sh -c '
    for source; do
      case $source in ./*/*)
        target="$(printf %sz "${source#./}" | tr / -)";
        mv -i -- "$source" "${target%z}";;
      esac
    done
' _ {} +

Explicación: la casedeclaración omite el directorio actual y los subdirectorios de nivel superior del directorio actual. targetcontiene el nombre del archivo de origen ( $0) con el principal ./despojado y todas las barras reemplazadas por guiones, más un final z. La final zestá allí en caso de que el nombre del archivo termine con una nueva línea: de lo contrario, la sustitución del comando lo eliminaría.

Si tu findno es compatible -exec … +(OpenBSD, te estoy mirando):

find . -depth -exec sh -c '
    case $0 in ./*/*)
      target="$(printf %sz "${0#./}" | tr / -)";
      mv -i -- "$0" "${target%z}";;
    esac
' {} \;

Con bash (o ksh93), no necesita llamar a un comando externo para reemplazar las barras por guiones, puede usar la expansión del parámetro ksh93 con la construcción de reemplazo de cadena ${VAR//STRING/REPLACEMENT}:

find . -depth -exec bash -c '
    for source; do
      case $source in ./*/*)
        source=${source#./}
        target="${source//\//-}";
        mv -i -- "$source" "$target";;
      esac
    done
' _ {} +
Gilles 'SO- deja de ser malvado'
fuente
Los for sourceejemplos pierden el primer archivo / directorio presentado ... Finalmente descubrí por qué usted (normalmente) ha utilizado _como $0:) ... y gracias por las excelentes respuestas. Aprendo mucho de ellos.
Peter.O
Tenga en cuenta que el ejemplo de zmv solo realiza una ejecución en seco: imprime los comandos de movimiento pero no los ejecuta. Para ejecutar realmente esos comandos, elimine la n en -Qn.
Peter
5

Si bien ya hay buenas respuestas aquí, encuentro esto más intuitivo, dentro de bash:

find aaa -type f -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); mv "{}" "$new"' \;

¿Dónde aaaestá tu directorio actual? Los archivos que contiene se moverán al directorio actual (dejando solo directorios vacíos en aaa). Las dos llamadas para trmanejar tanto directorios como espacios (sin lógica para las barras oblicuas escapadas, un ejercicio para el lector).

Considero que esto es más intuitivo y relativamente fácil de modificar. Es decir, podría cambiar los findparámetros si digo que solo quiero encontrar archivos .png. Puedo cambiar las trllamadas o agregar más según sea necesario. Y puedo agregar echoantes mvsi quiero verificar visualmente que hará lo correcto (luego repetir sin el extra echosolo si las cosas se ven bien:

find aaa -name \*.png -exec sh -c 'new=$(echo "{}" | tr "/" "-" | tr " " "_"); echo mv "{}" "$new"' \;

Tenga en cuenta también que estoy usando find aaa... y no find .... o find aaa/... ya que los dos últimos dejarían artefactos divertidos en el nombre del archivo final.

Dave Cohen
fuente
Gracias por este revestimiento: funcionó bien para mí cuando lo hice desde fuera del directorio (que es exactamente lo que dijiste que hiciera). Cuando intenté hacerlo desde dentro del directorio (con la esperanza de evitar que se agregara el nombre del directorio raíz) todos los archivos "desaparecieron". Los había convertido en archivos ocultos en ese mismo directorio.
dgig
2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Nota: esto no funcionará para el nombre de archivo que contiene \n.
Esto, por supuesto, solo mueve farchivos de tipo ...
Los únicos conflictos de nombres serían de archivos preexistentes en el pwd

Probado con este subconjunto básico

rm -fr junk
rm -f  junk*hello*

mkdir -p  junk/junkier/junkiest
touch    'hello    hello'
touch    'junk/hello    hello'
touch    'junk/junkier/hello    hello'
touch    'junk/junkier/junkiest/hello    hello'

Resultando en

./hello    hello
./junk-hello    hello
./junk-junkier-hello    hello
./junk-junkier-junkiest-hello    hello
Peter.O
fuente
0

Aquí hay una lsversión ... comentarios bienvenidos. Funciona para nombres de archivo como este "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", pero estoy realmente interesado en saber de cualquier escollo. Es más un ejercicio de "¿pueden los difamados lsrealmente hacerlo (con firmeza)?"

ls -AbQR1p * |                        # paths in "quoted" \escaped format
    sed -n '/":$/,${/.[^/]$/p}' |     # skip files in pwd, blank and dir/ lines
    { bwd="$PWD"; while read -n1;     # save base dir strip leading "quote
                        read -r p; do # read path
        printf -vs "${p%\"*}"         # strip trailing quote"(:)
        [[ $p == *: ]] && {           # this is a directory
            cd   "$bwd/$s"            # change into new directory
            rnp="${s//\//-}"-         # relative name prefix
        } || mv "$s" "$bwd/$rnp$s"
      done; }
Peter.O
fuente
-1

Simplemente canalice el nombre de archivo + ruta al trcomando.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done
Emcconville
fuente
1
Esto no maneja nombres de archivo extraños de forma segura. Los espacios lo romperán (entre otras cosas).
jw013
A primera vista, esta es una solución limpia y legible, pero @ jw013 tiene razón, no manejará bien los espacios. ¿Cambiaría la cplínea para cp -v "$FILE" "$NEWFILE"ayudar? (Además, no debe asumir solo archivos png.)
Nathan JB
2
@ NathanJ.Brauer Agregar comillas a la cplínea es insuficiente. Todo el enfoque de analizar la findsalida con un forbucle es defectuoso. Las únicas formas seguras de usar findson con -exec, o -print0si están disponibles (menos portátiles que -exec). Sugeriría una alternativa más sólida, pero su pregunta está subespecificada en este momento.
jw013
-2

Seguro para espacios en nombres de archivo:

#!/bin/bash

/bin/find $PWD -type f | while read FILE
do
    _new="${FILE//\//-}"
    _new="${_new:1}"
    #echo $FILE
    #echo $_new

    /bin/mv "$FILE" "$_new"
done

Corrió con el mío en cplugar de mvpor razones obvias, pero debería producir lo mismo.

Tim
fuente
3
type find-> find is /usr/bin/finden mi máquina. Usar PATHcomo nombre de variable en un script es una idea terrible. Además, su secuencia de comandos manipula los nombres de los archivos con espacios o barras diagonales inversas, ya que utiliza incorrectamente el readcomando (busque en este sitio IFS read -r).
Gilles 'SO- deja de ser malvado'
find is hashed (/bin/find), relevante cómo? Diferentes distribuciones son diferentes?
Tim
Sabía que debería haberme alejado de esto, cebo buitre.
Tim
@Tim Siempre puede eliminar la respuesta para recuperar su representante (y el mío también porque cuesta rep para votar a favor). La codificación de rutas en scripts parece sugerir que se está perdiendo el punto de la PATHvariable de entorno, que es algo que todo sistema Unix usa. Si escribe /bin/find, su script está realmente roto en cada sistema (Gilles, el mío y probablemente el OP) que no tiene /bin/find(por ejemplo, b / c está en /usr/bin/find). El objetivo de la PATHvariable de entorno es hacer que esto no sea un problema.
jw013
Por cierto, $(pwd)ya está disponible en una variable: $PWD. Pero debe no utilizar una ruta absoluta aquí: si el script se invoca desde, digamos, /home/tim, intenta cambiar el nombre /home/tim/some/patha /home-tim-some-path.
Gilles 'SO- deja de ser malvado'