Verzeichnis reduzieren, Verzeichnisnamen im neuen Dateinamen beibehalten

10

Wie kann ich ein Verzeichnis im folgenden Format reduzieren?

Vor: ./aaa/bbb/ccc.png

Nach: ./aaa-bbb-ccc.png

Nathan JB
quelle
PS: Bitte berücksichtigen Sie auch Dateinamen mit ungeraden Zeichen (z. B. Leerzeichen)
Nathan JB
3
Sie müssen auch angeben , wie Sie den möglichen Fall , in dem handhaben möchten ./foo/bar/baz.pngund ./foo-bar-baz.pngbeide existieren. Ich nehme an, Sie möchten Letzteres nicht durch Ersteres ersetzen?
jw013
In meinem einen Fall ist es irrelevant, aber die Antwort sollte dies tatsächlich berücksichtigen, damit sich andere darauf verlassen können! Vielen Dank!
Nathan JB

Antworten:

7

Warnung: Ich habe die meisten dieser Befehle direkt in meinen Browser eingegeben. Vorbehalt Lektor.

Mit zsh und zmv :

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

Erläuterung: Das Muster **/*stimmt rekursiv mit allen Dateien in Unterverzeichnissen des aktuellen Verzeichnisses überein (es stimmt nicht mit Dateien im aktuellen Verzeichnis überein, diese müssen jedoch nicht umbenannt werden). Die ersten beiden Klammerpaare sind Gruppen, auf die als $1und $2im Ersatztext verwiesen werden kann . Das letzte Klammerpaar fügt das D Glob-Qualifikationsmerkmal hinzu, damit Punktdateien nicht weggelassen werden. -o -ibedeutet, die -iOption an zu übergeben, mvdamit Sie gefragt werden, ob eine vorhandene Datei überschrieben werden soll.


Mit nur POSIX-Tools:

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

Erläuterung: In der caseAnweisung werden das aktuelle Verzeichnis und die Unterverzeichnisse der obersten Ebene des aktuellen Verzeichnisses weggelassen. targetenthält den Namen der Quelldatei ( $0), wobei der führende ./Strip entfernt und alle Schrägstriche durch Bindestriche ersetzt werden, sowie ein Finale z. Das Finale zist da, falls der Dateiname mit einer neuen Zeile endet: Andernfalls würde die Befehlsersetzung ihn entfernen.

Wenn Sie finddies nicht unterstützen -exec … +(OpenBSD, ich sehe Sie an):

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

Mit bash (oder ksh93) müssen Sie keinen externen Befehl aufrufen, um die Schrägstriche durch Bindestriche zu ersetzen. Sie können die Parametererweiterung ksh93 mit dem Konstrukt zum Ersetzen von Zeichenfolgen verwenden ${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 - hör auf böse zu sein'
quelle
In den for sourceBeispielen fehlt die erste vorgestellte Datei / Verzeichnis ... Ich habe endlich herausgefunden, warum Sie (normalerweise) _als $0:) verwendet haben ... und danke für die tollen Antworten. Ich lerne so viel von ihnen.
Peter.O
Beachten Sie, dass das zmv-Beispiel nur einen Probelauf ausführt: Es verschiebt die Verschiebungsbefehle, führt sie jedoch nicht aus. Um diese Befehle tatsächlich auszuführen, entfernen Sie das n in -Qn.
Peter
5

Obwohl es hier bereits gute Antworten gibt, finde ich dies intuitiver in bash:

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

Wo aaaist Ihr vorhandenes Verzeichnis? Die darin enthaltenen Dateien werden in das aktuelle Verzeichnis verschoben (wobei nur leere Verzeichnisse in aaa verbleiben). Die beiden Aufrufe, trsowohl Verzeichnisse als auch Leerzeichen zu behandeln (ohne Logik für Schrägstriche - eine Übung für den Leser).

Ich halte dies für intuitiver und relativ einfach zu optimieren. Dh ich könnte die findParameter ändern , wenn ich nur PNG-Dateien finden möchte. Ich kann die trAnrufe ändern oder bei Bedarf weitere hinzufügen. Und ich kann echovorher hinzufügen, mvwenn ich visuell überprüfen möchte, ob es das Richtige tut (dann ohne das Extra echonur wiederholen, wenn die Dinge richtig aussehen:

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

Beachten Sie auch, dass ich find aaa... und nicht find .... oder find aaa/... verwende, da die beiden letzteren lustige Artefakte im endgültigen Dateinamen hinterlassen würden.

Dave Cohen
quelle
Vielen Dank für diesen einen Liner - das hat bei mir gut funktioniert, als ich es von außerhalb des Verzeichnisses gemacht habe (genau das haben Sie gesagt). Als ich versuchte, aus dem Verzeichnis heraus zu tun (in der Hoffnung, dass der Name des Stammverzeichnisses nicht hinzugefügt wird), "verschwanden" alle Dateien. Ich hatte sie in versteckte Dateien im selben Verzeichnis umgewandelt.
dgig
2
find . -mindepth 2 -type f -name '*' |
  perl -l000ne 'print $_;  s/\//-/g; s/^\.-/.\// and print' |
    xargs -0n2 mv 

Hinweis: Dies funktioniert nicht für Dateinamen, die enthalten \n.
Dies verschiebt natürlich nur Typdateien f...
Die einzigen Namenskonflikte würden von Dateien stammen, die bereits im pwd vorhanden sind

Getestet mit dieser grundlegenden Teilmenge

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'

Ergebend

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

Hier ist eine lsVersion .. Kommentare willkommen. Es funktioniert für verrückte Dateinamen wie diesen "j1ळ\n\001\n/j2/j3/j4/\nh  h\n", aber ich bin wirklich interessiert, irgendwelche Fallstricke zu kennen. Es ist eher eine Übung in "Kann der Bösartige es lstatsächlich (robust) tun?"

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
quelle
-1

Leiten Sie einfach den Dateinamen + den Pfad zum trBefehl weiter.tr <replace> <with>

for FILE in `find aaa -name "*.png"`
do
  NEWFILE=`echo $FILE | tr '/' '-'`
  cp -v $FILE $NEWFILE
done
Emcconville
quelle
1
Dies behandelt ungerade Dateinamen nicht sicher. Räume werden es (unter anderem) brechen.
jw013
Auf den ersten Blick ist dies eine saubere, lesbare Lösung, aber @ jw013 ist richtig, es wird nicht gut mit Leerzeichen umgehen. Würde cpes cp -v "$FILE" "$NEWFILE"helfen, die Leitung zu ändern ? (Außerdem sollten Sie nicht nur PNG-Dateien annehmen.)
Nathan JB
2
@ NathanJ.Brauer Das Hinzufügen von Anführungszeichen zur cpZeile ist unzureichend. Der gesamte Ansatz zum Parsen der findAusgabe mit einer forSchleife ist fehlerhaft. Die einzigen sicheren Verwendungsmöglichkeiten findsind mit -execoder, -print0falls verfügbar (weniger tragbar als -exec). Ich würde eine robustere Alternative vorschlagen, aber Ihre Frage ist derzeit nicht ausreichend spezifiziert.
jw013
-2

Sicher für Leerzeichen in Dateinamen:

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

Lief meine mit cpstatt aus mvoffensichtlichen Gründen, sollte aber das gleiche produzieren.

Tim
quelle
3
type find-> find is /usr/bin/findauf meiner Maschine. Die Verwendung PATHals Variablenname in einem Skript ist eine schreckliche Idee. Außerdem werden in Ihrem Skript Dateinamen mit Leerzeichen oder umgekehrten Schrägstrichen versehen, da Sie den readBefehl missbrauchen (suchen Sie auf dieser Site nach IFS read -r).
Gilles 'SO - hör auf böse zu sein'
find is hashed (/bin/find), relevant wie? Unterschiedliche Distributionen sind unterschiedlich?
Tim
Ich wusste, ich hätte mich von diesem Geierköder fernhalten sollen.
Tim
@Tim Sie können die Antwort jederzeit löschen, um Ihren Repräsentanten zurückzubekommen (und meiner auch b / c, es kostet Repräsentanten, um sie abzustimmen). Hartcodierte Pfade in Skripten scheinen darauf hinzudeuten, dass Ihnen der Punkt der PATHUmgebungsvariablen fehlt, den jedes Unix-System verwendet. Wenn Sie schreiben, /bin/findist Ihr Skript tatsächlich auf jedem System (Gilles, meins und wahrscheinlich den OPs) defekt, das es nicht gibt /bin/find(z. B. b / c, in dem es sich befindet /usr/bin/find). Der Sinn der PATHUmgebungsvariablen besteht darin, dies zu einem Nicht-Problem zu machen.
jw013
Ist übrigens $(pwd)schon in einer Variablen verfügbar : $PWD. Aber Sie müssen nicht einen absoluten Pfad hier verwenden: Wenn Ihr Skript aus, sagen wir aufgerufen wird, /home/timversucht es umbenennen /home/tim/some/pathzu /home-tim-some-path.
Gilles 'SO - hör auf böse zu sein'