Aplanir le répertoire mais conserver les noms de répertoire dans un nouveau nom de fichier

10

Comment aplatir un répertoire au format suivant?

Avant: ./aaa/bbb/ccc.png

Après: ./aaa-bbb-ccc.png

Nathan JB
la source
PS: Veuillez également tenir compte des noms de fichiers avec des caractères impairs (par exemple des espaces)
Nathan JB
3
Vous devrez également spécifier comment vous souhaitez gérer le cas possible où ./foo/bar/baz.pnget les ./foo-bar-baz.pngdeux existent. Je suppose que vous ne voulez pas remplacer ce dernier par l'ancien?
jw013
Dans mon cas, ce n'est pas pertinent, mais la réponse devrait en effet en tenir compte pour que les autres puissent s'y fier! Merci!
Nathan JB

Réponses:

7

Avertissement: j'ai tapé la plupart de ces commandes directement dans mon navigateur. Caveat lector.

Avec zsh et zmv :

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

Explication: Le modèle **/*correspond à tous les fichiers des sous-répertoires du répertoire actuel, de manière récursive (il ne correspond pas aux fichiers du répertoire actuel, mais ceux-ci n'ont pas besoin d'être renommés). Les deux premières paires de parenthèses sont des groupes auxquels on peut se référer en tant que $1et $2dans le texte de remplacement. La dernière paire de parenthèses ajoute le D qualificatif glob afin que les fichiers de points ne soient pas omis. -o -isignifie passer l' -ioption à mvafin que vous soyez invité si un fichier existant serait écrasé.


Avec seulement les outils POSIX:

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

Explication: l' caseinstruction omet le répertoire en cours et les sous-répertoires de niveau supérieur du répertoire en cours. targetcontient le nom du fichier source ( $0) avec le début ./supprimé et toutes les barres obliques remplacées par des tirets, plus une finale z. La finale zest là au cas où le nom de fichier se termine par une nouvelle ligne: sinon la substitution de commande le supprimerait.

Si votre findne prend pas en charge -exec … +(OpenBSD, je vous regarde):

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

Avec bash (ou ksh93), vous n'avez pas besoin d'appeler une commande externe pour remplacer les barres obliques par des tirets, vous pouvez utiliser l'expansion du paramètre ksh93 avec la construction de remplacement de chaîne ${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- arrête d'être méchant'
la source
Les for sourceexemples manquent le premier fichier / répertoire présenté ... J'ai enfin trouvé pourquoi vous (normalement) l'avez utilisé _comme $0:) ... et merci pour les bonnes réponses. J'apprends tellement d'eux.
Peter.O
Notez que l'exemple zmv ne fait qu'un essai à sec: il imprime les commandes de déplacement mais ne les exécute pas. Pour exécuter réellement ces commandes, supprimez le n dans -Qn.
Peter
5

Bien qu'il existe déjà de bonnes réponses ici, je trouve cela plus intuitif, dans bash:

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

aaaest votre répertoire existant. Les fichiers qu'il contient seront déplacés vers le répertoire courant (ne laissant que des répertoires vides dans aaa). Les deux appels pour trgérer à la fois les répertoires et les espaces (sans logique pour les barres obliques échappées - un exercice pour le lecteur).

Je considère cela plus intuitif et relativement facile à modifier. C'est-à-dire que je pourrais changer les findparamètres si je dis que je veux trouver uniquement les fichiers .png. Je peux modifier les trappels ou en ajouter d'autres au besoin. Et je peux ajouter echoavant mvsi je veux vérifier visuellement qu'il fera la bonne chose (puis répéter sans extra echouniquement si les choses semblent correctes:

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

Notez également que j'utilise find aaa... et non find .... ou find aaa/... car les deux derniers laisseraient des artefacts amusants dans le nom de fichier final.

Dave Cohen
la source
Merci pour cette doublure - cela a bien fonctionné pour moi quand je l'ai fait en dehors du répertoire (ce qui est exactement ce que vous avez dit de faire). Lorsque j'ai essayé de le faire depuis l'intérieur du répertoire (en espérant éviter que le nom du répertoire racine ne soit ajouté), tous les fichiers ont "disparu". Je les avais transformés en fichiers cachés dans ce même répertoire.
dgig
2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Remarque: cela ne fonctionnera pas pour le nom de fichier qui contient \n.
Ceci, bien sûr, ne déplace que les ffichiers de type ...
Les seuls conflits de noms proviendraient de fichiers préexistants dans le pwd

Testé avec ce sous-ensemble de base

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'

Résultant en

./hello    hello
./junk-hello    hello
./junk-junkier-hello    hello
./junk-junkier-junkiest-hello    hello
Peter.O
la source
0

Voici une lsversion .. commentaires bienvenus. Cela fonctionne pour les noms de fichiers loufoques comme celui-ci "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", mais je suis vraiment intéressé de connaître les pièges. Il s'agit plus d'un exercice dans "les calomniés peuvent-ils lsvraiment le faire (avec robustesse)?"

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
la source
-1

Il suffit de diriger le nom de fichier + le chemin vers la trcommande.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done
emcconville
la source
1
Cela ne gère pas les noms de fichiers impairs en toute sécurité. Les espaces le briseront (entre autres).
jw013
À première vue, c'est une solution propre et lisible, mais @ jw013 a raison, il ne gérera pas bien les espaces. Changerait la cpligne pour cp -v "$FILE" "$NEWFILE"aider? (En outre, vous ne devez pas supposer uniquement les fichiers png.)
Nathan JB
2
@ NathanJ.Brauer L'ajout de guillemets à la cpligne est insuffisant. L'approche complète de l'analyse de la findsortie avec une forboucle est défectueuse. Les seuls moyens d'utilisation sûrs findsont avec -execou -print0s'ils sont disponibles (moins portables que -exec). Je suggérerais une alternative plus robuste mais votre question est sous-spécifiée pour le moment.
jw013
-2

Sans danger pour les espaces dans les noms de fichiers:

#!/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

Ran mine avec cpau lieu de mvpour des raisons évidentes, mais devrait produire la même chose.

Tim
la source
3
type find-> find is /usr/bin/findsur ma machine. L'utilisation PATHcomme nom de variable dans un script est une très mauvaise idée. En outre, votre script modifie les noms de fichiers avec des espaces ou des barres obliques inverses, car vous utilisez la readcommande à mauvais escient (recherchez ce site IFS read -r).
Gilles 'SO- arrête d'être méchant'
find is hashed (/bin/find), pertinent comment? Différentes distributions sont différentes?
Tim
Je savais que j'aurais dû rester loin de ça, appât vautour.
Tim
@Tim Vous pouvez toujours supprimer la réponse pour récupérer votre représentant (et le mien aussi, car cela coûte au représentant de voter). Les chemins codés en dur dans les scripts semblent suggérer que vous manquez le point de la PATHvariable d'environnement, qui est quelque chose que tout système Unix utilise. Si vous écrivez, /bin/findvotre script est en fait cassé sur tous les systèmes (Gilles, le mien et probablement les OP) qui n'en ont pas /bin/find(par exemple, b / c il est dedans /usr/bin/find). L'intérêt de la PATHvariable d'environnement est d'en faire un non-problème.
jw013
Soit dit en passant, $(pwd)est déjà disponible dans une variable: $PWD. Mais vous devez ne pas utiliser un chemin absolu ici: si votre script est appelé à partir, par exemple, /home/timil tente de renommer /home/tim/some/pathà /home-tim-some-path.
Gilles 'SO- arrête d'être méchant'